如何在基于create-react-app的项目中添加字体?

399

我正在使用create-react-app,并且不希望进行eject操作。

不清楚通过@font-face导入并在本地加载的字体应该放在哪里。

特别是,我正在加载:

@font-face {
  font-family: 'Myriad Pro Regular';
  font-style: normal;
  font-weight: normal;
  src: local('Myriad Pro Regular'), url('MYRIADPRO-REGULAR.woff') format('woff');
}

任何建议? -- 编辑 在他的答案中包括Dan提到的要点
➜  Client git:(feature/trivia-game-ui-2) ✗ ls -l public/static/fonts
total 1168
-rwxr-xr-x@ 1 maximveksler  staff  62676 Mar 17  2014 MYRIADPRO-BOLD.woff
-rwxr-xr-x@ 1 maximveksler  staff  61500 Mar 17  2014 MYRIADPRO-BOLDCOND.woff
-rwxr-xr-x@ 1 maximveksler  staff  66024 Mar 17  2014 MYRIADPRO-BOLDCONDIT.woff
-rwxr-xr-x@ 1 maximveksler  staff  66108 Mar 17  2014 MYRIADPRO-BOLDIT.woff
-rwxr-xr-x@ 1 maximveksler  staff  60044 Mar 17  2014 MYRIADPRO-COND.woff
-rwxr-xr-x@ 1 maximveksler  staff  64656 Mar 17  2014 MYRIADPRO-CONDIT.woff
-rwxr-xr-x@ 1 maximveksler  staff  61848 Mar 17  2014 MYRIADPRO-REGULAR.woff
-rwxr-xr-x@ 1 maximveksler  staff  62448 Mar 17  2014 MYRIADPRO-SEMIBOLD.woff
-rwxr-xr-x@ 1 maximveksler  staff  66232 Mar 17  2014 MYRIADPRO-SEMIBOLDIT.woff
➜  Client git:(feature/trivia-game-ui-2) ✗ cat src/containers/GameModule.css
.GameModule {
  padding: 15px;
}

@font-face {
  font-family: 'Myriad Pro Regular';
  font-style: normal;
  font-weight: normal;
  src: local('Myriad Pro Regular'), url('%PUBLIC_URL%/static/fonts/MYRIADPRO-REGULAR.woff') format('woff');
}

@font-face {
  font-family: 'Myriad Pro Condensed';
  font-style: normal;
  font-weight: normal;
  src: local('Myriad Pro Condensed'), url('%PUBLIC_URL%/static/fonts/MYRIADPRO-COND.woff') format('woff');
}

@font-face {
  font-family: 'Myriad Pro Semibold Italic';
  font-style: normal;
  font-weight: normal;
  src: local('Myriad Pro Semibold Italic'), url('%PUBLIC_URL%/static/fonts/MYRIADPRO-SEMIBOLDIT.woff') format('woff');
}

@font-face {
  font-family: 'Myriad Pro Semibold';
  font-style: normal;
  font-weight: normal;
  src: local('Myriad Pro Semibold'), url('%PUBLIC_URL%/static/fonts/MYRIADPRO-SEMIBOLD.woff') format('woff');
}

@font-face {
  font-family: 'Myriad Pro Condensed Italic';
  font-style: normal;
  font-weight: normal;
  src: local('Myriad Pro Condensed Italic'), url('%PUBLIC_URL%/static/fonts/MYRIADPRO-CONDIT.woff') format('woff');
}

@font-face {
  font-family: 'Myriad Pro Bold Italic';
  font-style: normal;
  font-weight: normal;
  src: local('Myriad Pro Bold Italic'), url('%PUBLIC_URL%/static/fonts/MYRIADPRO-BOLDIT.woff') format('woff');
}

@font-face {
  font-family: 'Myriad Pro Bold Condensed Italic';
  font-style: normal;
  font-weight: normal;
  src: local('Myriad Pro Bold Condensed Italic'), url('%PUBLIC_URL%/static/fonts/MYRIADPRO-BOLDCONDIT.woff') format('woff');
}

@font-face {
  font-family: 'Myriad Pro Bold Condensed';
  font-style: normal;
  font-weight: normal;
  src: local('Myriad Pro Bold Condensed'), url('%PUBLIC_URL%/static/fonts/MYRIADPRO-BOLDCOND.woff') format('woff');
}

@font-face {
  font-family: 'Myriad Pro Bold';
  font-style: normal;
  font-weight: normal;
  src: local('Myriad Pro Bold'), url('%PUBLIC_URL%/static/fonts/MYRIADPRO-BOLD.woff') format('woff');
}

9
你有查看该软件的用户指南中的“添加字体”部分吗? - Dan Abramov
4
@DanAbramov 我已经收到了建议,是导入字体。但我觉得实际操作起来不是很清晰(至少对我来说不是)。同时我做了这个 https://gist.github.com/maximveksler/5b4f80c5ded20237c3deebc82a31dcd5 ,它似乎可以工作(如果 webpack 找不到字体文件就会弹出警告),但我确定这不是最佳解决方案,提供一个示例或在此处记录下来将会有所帮助。感谢你的联系! - Maxim Veksler
2
我回答了。你的方法在我看来是错误的:%PUBLIC_URL% 在 CSS 文件中无法工作(也是不必要的)。此外,正如指南中所述,在几乎所有情况下,你应该使用 JS 导入而不是公共文件夹。 - Dan Abramov
有没有任何实用程序/包可以扫描指定文件夹中的字体并生成包含所有字体变体的脚本文件?手动编写所有内容很繁琐。 - helloworld
我来这里的目的是为了能够“导入”字体,因为我正在使用react-pdf,并且想要导入实际的字体文件,而不是css。感谢任何帮助。 - jasxir
12个回答

618

有两个选项:

使用导入

这是建议的选项。它可以确保您的字体通过构建流程,编译期间获得哈希值,使浏览器缓存正确工作,并且如果文件丢失,则会收到编译错误。

如“添加图像、字体和文件”中所述,您需要从JS导入CSS文件。例如,默认情况下src/index.js导入src/index.css

import './index.css';

像这样的CSS文件会通过构建流程,并且可以引用字体和图像。例如,如果您将字体放在 src/fonts/MyFont.woff 中,您的 index.css 可能包含以下内容:

@font-face {
  font-family: 'MyFont';
  src: local('MyFont'), url(./fonts/MyFont.woff) format('woff');
  /* other formats include: 'woff2', 'truetype, 'opentype',
                            'embedded-opentype', and 'svg' */
}

注意我们正在使用以./开头的相对路径。这是一种特殊的标记,帮助构建流水线(由Webpack驱动)发现此文件。

通常这就足够了。

使用public文件夹

如果出于某些原因您更喜欢不使用构建流水线,而是使用“传统方式”,您可以使用public文件夹并将字体放在那里。

这种方法的缺点是,在为生产环境编译时,这些文件不会得到哈希,因此每次更改它们时都必须更新它们的名称,否则浏览器将缓存旧版本。

如果您想以这种方式进行操作,请将字体放置在public文件夹中的某个地方,例如public/fonts/MyFont.woff。如果您采用此方法,还应将CSS文件放入public文件夹中,而不要从JS中导入它们,因为混合这些方法会非常令人困惑。因此,如果您仍然想这样做,您将拥有一个类似于public/index.css的文件。您必须从public/index.html手动添加<link>到此样式表中:

<link rel="stylesheet" href="%PUBLIC_URL%/index.css">

在其中,您将使用常规CSS符号:

@font-face {
  font-family: 'MyFont';
  src: local('MyFont'), url(fonts/MyFont.woff) format('woff');
}

注意我是如何使用fonts/MyFont.woff作为路径的。这是因为index.csspublic文件夹中,所以它将从公共路径提供服务(通常是服务器根目录,但如果您部署到GitHub Pages并将homepage字段设置为http://myuser.github.io/myproject,它将从/myproject提供服务)。然而,fonts也在public文件夹中,因此它们将从相对路径的fonts提供服务(可以是http://mywebsite.example/fontshttp://myuser.github.io/myproject/fonts)。因此,我们使用相对路径。

请注意,由于我们在此示例中避免使用构建流程,因此它不会验证文件是否实际存在。这就是为什么我不建议使用这种方法的原因之一。另一个问题是我们的index.css文件不会被压缩和哈希处理。因此,它对终端用户来说速度会慢些,并且你有可能会冒着浏览器缓存旧版本文件的风险。

使用哪种方式?

采用第一种方法(“使用引入”)。我只描述了第二种方法,因为这是你尝试做的(根据你的评论判断),但它有很多问题,应该只在你需要解决某些问题时作为最后的备选方案。


9
文档中提供一个示例会很有帮助,我也有点困惑。 - Tom
134
如果有人添加一个 ttf 字体,你应该将 format 的参数给定为 truetype 而不是 ttf* - milkersarac
40
如果你使用的是 otf,那么请在后面加上 opentype - Karl Taylor
2
@milkersarac的评论应该被添加到答案中 - 这个救了我。 - blubberbo
3
一个家族可以有多种样式(boldbold+italic等)吗?这个链接能帮到你:https://dev59.com/jHE95IYBdhLWcg3wJKh_#2436830 - Husterknupp
显示剩余9条评论

179

以下是一些方法:

1. 导入字体

例如,要使用Roboto,请使用以下命令安装包:

yarn add typeface-roboto
npm install typeface-roboto --save

在 index.js 中:

import "typeface-roboto";

有很多开源字体的 npm 包,大部分 Google 字体也有。你可以在这里看到所有字体包,所有包都来自于该项目

2. 对于由第三方托管的字体

例如 Google 字体,你可以访问 fonts.google.com,在那里你可以找到链接并将其放入public/index.html文件中。

fonts.google.com 的截图

就像这样:

<link href="https://fonts.googleapis.com/css?family=Montserrat" rel="stylesheet">
<style>
    @import url('https://fonts.googleapis.com/css?family=Montserrat');
</style>

3. 下载字体并将其添加到源代码中。

下载字体。例如,对于Google字体,您可以前往fonts.google.com。单击下载按钮以下载字体。

将字体移动到位于src目录下的fonts目录中。

src
|
`----fonts
|      |
|      `-Lato/Lato-Black.ttf
|       -Lato/Lato-BlackItalic.ttf
|       -Lato/Lato-Bold.ttf
|       -Lato/Lato-BoldItalic.ttf
|       -Lato/Lato-Italic.ttf
|       -Lato/Lato-Light.ttf
|       -Lato/Lato-LightItalic.ttf
|       -Lato/Lato-Regular.ttf
|       -Lato/Lato-Thin.ttf
|       -Lato/Lato-ThinItalic.ttf
|
`----App.css

现在,在App.css文件中添加以下内容

@font-face {
  font-family: 'Lato';
  src: local('Lato'), url(./fonts/Lato-Regular.otf) format('opentype');
}

@font-face {
    font-family: 'Lato';
    font-weight: 900;
    src: local('Lato'), url(./fonts/Lato-Bold.otf) format('opentype');
}

@font-face {
    font-family: 'Lato';
    font-weight: 900;
    src: local('Lato'), url(./fonts/Lato-Black.otf) format('opentype');
}

对于ttf格式,你需要提及format('truetype')。对于woff格式,format('woff')

现在你可以在类中使用字体。

.modal-title {
    font-family: Lato, Arial, serif;
    font-weight: black;
}

4. 使用 web-font-loader 包

使用以下命令安装该包:

yarn add webfontloader
npm install webfontloader --save
src/index.js 中,您可以导入它并指定所需的字体。
import WebFont from 'webfontloader';

WebFont.load({
   google: {
     families: ['Titillium Web:300,400,700', 'sans-serif']
   }
});

@sudobangbang 谢谢,第三个解决方案对我有用。不过,是否有一种方法可以将 fonts 文件夹放在 public 下而不是放在 src 下?我尝试过了,但似乎不被允许... - Yossi Vainshtein
3
对于ttf格式,您需要提及format('truetype')。对于woff格式,使用format('woff')即可。这对我很有帮助!谢谢! - Rip3rs
1
字体类型已经被弃用,使用 fontsource 代替:https://github.com/fontsource/fontsource - Hugo Elhaj-Lahsen
谢谢,这很有帮助。请将 font-family: 'Lato'; 更新为不同的名称以使用不同类型的字体,例如 LatoRegular、LatoBold 等等。 - adak
同样适用于Tailwind CSS。谢谢! - Owais Sonija
显示剩余2条评论

11
  1. 前往Google Fonts页面https://fonts.google.com/
  2. 按照下图所示选择您的字体:

enter image description here

  1. 复制并粘贴该URL到新标签中,即可获取添加该字体的CSS代码。在本例中,若您访问

https://fonts.googleapis.com/css?family=Spicy+Rice

会打开如下页面:

enter image description here

4. 复制并粘贴该代码到style.css文件中,然后就可以像这样开始使用这个字体了:

      <Typography
          variant="h1"
          gutterBottom
          style={{ fontFamily: "Spicy Rice", color: "pink" }}
        >
          React Rock
        </Typography>

结果:

在此输入图片描述


该结果显示了一张图片,无法提供更多信息。

3
在许多情况下(例如:企业网络),由于防火墙的限制,无法访问外部CDN,因此即使该方法是正确的,也可能无效。我们的组织中有多个VLAN,除了IT和其他几个人以外,所有VLAN都会阻止外部CDN的访问,这也意味着谷歌的内容CDN也被阻止了。已经尝试过,但没有成功。 - Anjan Biswas

7
使用不同的字体文件来区分正常/斜体font-style时,根据入口点指定@font-face的方式可能需要不同。如果选择将css文件直接链接到public/index.html中,则可以使用一个font-family名称和不同的font-style来正常使用font-face。请参阅我在这里的回答
@font-face {
  font-family: "FontName"; <---
  font-style: normal; <---
  font-weight: normal;
  src: url("path-to-assets/fonts/FontName.ttf") format("truetype");
}
@font-face {
  font-family: "FontName"; <---
  font-style: italic; <---
  font-weight: normal;
  src: url("path-to-assets/fonts/FontName-Italic.ttf") format("truetype");
}

/* Usage */
.text {
  font-family: FontName;
  font-style: normal;
}
.text-italic {
  font-family: FontName;
  font-style: italic;
}
  1. 如果您选择通过Js进行捆绑链接css文件,那么您需要为所有italic字体设置不同的font-family名称,并使用font-style normal。
@font-face {
  font-family: "FontName"; <---
  font-style: normal; <---
  font-weight: normal; /* normal | 300 | 400 | 600 | bold | etc */
  src: url("path-to-assets/fonts/FontName.ttf") format("truetype");
}
@font-face {
  font-family: "FontNameItalic";
  font-style: normal; <----
  font-weight: normal; /* normal | 300 | 400 | 600 | bold | etc */
  src: url("path-to-assets/fonts/FontName-Italic.ttf") format("truetype");
}

/* Usage */
.text {
  font-family: FontName;
}
.text-italic {
  font-family: FontNameItalic;
}

5
你可以使用Web API FontFace构造函数(也适用于Typescript),无需CSS:
export async function loadFont(fontFamily: string, url: string): Promise<void> {
    const font = new FontFace(fontFamily, `local(${fontFamily}), url(${url})`);
    // wait for font to be loaded
    await font.load();
    // add font to document
    document.fonts.add(font);
    // enable font with CSS class
    document.body.classList.add("fonts-loaded");
}

import ComicSans from "./assets/fonts/ComicSans.ttf";

loadFont("Comic Sans ", ComicSans).catch((e) => {
    console.log(e);
});

声明一个名为font.ts的文件,并将其与你的模块一起使用(仅限TS):

declare module "*.ttf";
declare module "*.woff";
declare module "*.woff2";

如果TS无法找到FontFace类型,因为它仍处于官方的WIP状态,请将this declaration添加到您的项目中。它将在您的浏览器中工作,但IE除外。

5

本地字体链接可能会导致React JS失败。因此,我更喜欢使用来自谷歌的在线CSS文件来链接字体。请参考以下代码:

<link href="https://fonts.googleapis.com/css?family=Roboto" rel="stylesheet">

或者

<style>
    @import url('https://fonts.googleapis.com/css?family=Roboto');
</style>

3
你可以使用WebFont模块,它可以极大地简化该过程。
render(){
  webfont.load({
     custom: {
       families: ['MyFont'],
       urls: ['/fonts/MyFont.woff']
     }
  });
  return (
    <div style={your style} >
      your text!
    </div>
  );
}

1

我犯了像这样的错误。

@import "https://fonts.googleapis.com/css?family=Open+Sans:300,300i,400,400i,600,600i,700,700i&amp;subset=cyrillic,cyrillic-ext,latin-ext";
@import "https://use.fontawesome.com/releases/v5.3.1/css/all.css";

这样做可以正常运行。
@import url(https://fonts.googleapis.com/css?family=Open+Sans:300,300i,400,400i,600,600i,700,700i&amp;subset=cyrillic,cyrillic-ext,latin-ext);
@import url(https://use.fontawesome.com/releases/v5.3.1/css/all.css);

1
我添加了。
@font-face {
    font-family: 'Sanchez-Regular';
    src: local('Sanchez-Regular'),url(../assets/fonts/Sanchez/Sanchez-Regular.ttf) format('truetype');
}

它非常有效,只需在index.css中完成后,就像我们使用其他字体一样使用它。


1

在看到这个问题后,我花了整个早上时间来解决类似的问题。我使用了丹尼尔在上面回答中的第一个解决方案作为起点。

问题

我有一个开发环境(本地机器)、演示环境和生产环境。我的演示环境和生产环境位于同一台服务器上。

应用程序通过acmeserver/~staging/note-taking-app部署到演示环境,而生产版本则位于acmeserver/note-taking-app(怪罪IT)。

所有媒体文件,如字体,在开发过程中都能完美加载(即react-scripts start)。

然而,当我创建并上传演示和生产构建版本时,虽然.css.js文件都成功加载了,但字体却没有。编译好的.css文件路径看起来是正确的,但浏览器的http请求获取的路径却非常错误(如下所示)。

编译好的main.fc70b10f.chunk.css文件:

@font-face {
  font-family: SairaStencilOne-Regular;
  src: url(note-taking-app/static/media/SairaStencilOne-Regular.ca2c4b9f.ttf) ("truetype");
}

浏览器的http请求如下所示。请注意,当字体文件只存在于/static/media/中时,它会添加/static/css/,并重复目标文件夹。我排除了服务器配置是罪魁祸首的可能性。
Referer也有一部分责任。
GET /~staging/note-taking-app/static/css/note-taking-app/static/media/SairaStencilOne-Regular.ca2c4b9f.ttf HTTP/1.1
Host: acmeserver
Origin: http://acmeserver
Referer: http://acmeserver/~staging/note-taking-app/static/css/main.fc70b10f.chunk.css

package.json文件的homepage属性被设置为./note-taking-app,这是引起问题的原因。

{
  "name": "note-taking-app",
  "version": "0.1.0",
  "private": true,
  "homepage": "./note-taking-app",
  "scripts": {
    "start": "env-cmd -e development react-scripts start",
    "build": "react-scripts build",
    "build:staging": "env-cmd -e staging npm run build",
    "build:production": "env-cmd -e production npm run build",
    "test": "react-scripts test",
    "eject": "react-scripts eject"
  }
  //...
}

解决方案

虽然有些冗长,但解决方案如下:

  1. 根据环境更改 PUBLIC_URL 环境变量
  2. package.json 文件中删除 homepage 属性

以下是我的 .env-cmdrc 文件。我使用 .env-cmdrc 而不是普通的 .env,因为它将所有内容都放在一个文件中。

{
  "development": {
    "PUBLIC_URL": "",
    "REACT_APP_API": "http://acmeserver/~staging/note-taking-app/api"
  },
  "staging": {
    "PUBLIC_URL": "/~staging/note-taking-app",
    "REACT_APP_API": "http://acmeserver/~staging/note-taking-app/api"
  },
  "production": {
    "PUBLIC_URL": "/note-taking-app",
    "REACT_APP_API": "http://acmeserver/note-taking-app/api"
  }
}

使用react-router-dom进行路由也可以正常工作 - 只需将PUBLIC_URL环境变量用作basename属性即可。

import React from "react";
import { BrowserRouter } from "react-router-dom";

const createRouter = RootComponent => (
  <BrowserRouter basename={process.env.PUBLIC_URL}>
    <RootComponent />
  </BrowserRouter>
);

export { createRouter };

服务器配置被设置为将所有请求路由到./index.html文件。
最后,这是经过讨论更改后编译的main.fc70b10f.chunk.css文件的样子。
@font-face {
  font-family: SairaStencilOne-Regular;
  src: url(/~staging/note-taking-app/static/media/SairaStencilOne-Regular.ca2c4b9f.ttf)
    format("truetype");
}

阅读材料


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接