CSS之Web字体

1,719 阅读7分钟

如果我们想使用一种不是预先安装在用户设备上的字体,我们可以下载并使用自定义Web字体!本文介绍几种实现方式。

使用Google字体

谷歌字体是一个免费的、开源的网络字体库,它们有数百种流行的选择。

它还可以有效地作为字体的 CDN; 它们通过自己的服务器为我们提供字体。

谷歌字体的工作原理是提供这样一个代码片段:

<link rel="preconnect" href="<https://fonts.gstatic.com>">
<link href="<https://fonts.googleapis.com/css2?family=Open+Sans:ital,wght@1,400;1,600&display=swap>" rel="stylesheet">

把它放到 HTML 文件的 `` 中。

这个代码片段会下载一个样式表来下载一个字体,我们可以在 CSS 中访问这个字体:

.thing {
  font-family:'Open Sans', sans-serif;
}

Web 字体应该用引号包装(“Open Sans”,而不是Open Sans)。如果你的Web字体是一个单词,这并不是严格要求的,但它是一个有用的约定: 它指出堆栈中的字体是Web字体还是本地字体。

谷歌字体的主要好处是它很漂亮,很容易使用。缺点是很多令人惊叹的字体在谷歌字体上是无法使用;自我托管的Web字体反而性能更好。

Gatsby的创始人Kyle Matthews发现,自托管字体可以在桌面设备上节省300毫秒,在移动3G设备上节省1秒以上

为什么使用Goolge字体反而会更慢呢?抛开网络环境因素(墙)和服务器因素(Google的服务器肯定是性能很好的),其实主要原因还是Google字体的使用方式。

让我们来看看使用上面Google的HTTP代码会发生什么:

  1. 浏览器解析index.html文件
  2. 当解析到<link href="https://fonts.googleapis.com/css2">标签时,会发送一个HTTP请求来获取CSS文件
  3. 浏览器解析CSS文件,发现有Web字体需要下载,于是从发送请求到 fonts.gstatic.com 来获取字体文件。

从上面的步骤可以看到CSS文件与字体文件是放置在不同的外部域名上的,这就不可避免的增加了网络开销(如HTTP三次握手)。而对CSS文件的请求是会阻塞页面渲染的。相比之下自托管的Web字体反而更有优势,因为它可以将CSS内嵌到HTML文件中,跳过了其中的的第二步;并且字体文件存储在同一服务器上,也可以减少很多网络开销(减少了握手次数)

在过去,浏览器有一个全局缓存可以抵消这一点; 有可能你的用户已经有一个缓存版本的字体,因为不同的站点从同一个 CDN 获取了字体。然而,出于隐私方面的考虑,浏览器已经开始使用分区缓存。这意味着你的用户在第一次访问时总是需要下载你的网页字体,即使他们已经访问的其他网站使用了从相同的CDN获取的相同字体。

使用Fontsource打包字体

Next.js框架背后的公司Vercel发布了一个有趣的免费的制作自托管字体资源Fontsource

它提供了一个易于使用的方法来安装和使用自托管的 web 字体。它是专门为现代 JS 应用程序构建的,让你 NPM 安装你想要使用的字体。它们支持所有的 Google 字体,以及额外的免费开源字体。

手动方式

如果你想使用 Google 字体或者 Fontsource 上没有的字体,该怎么办?

那就可以通过手动的方式添加了

转换格式

在我们的电脑上运行的字体通常是.otf.ttf 文件格式。这些文件格式不适合在web上使用,而且它们的文件往往相对庞大。

我们首先必须将我们的字体转换成一种web友好的格式。你可以使用在线转换工具,比如 Fontsquirrel 的 webfont generator

font-face

接下来我们使用@font-face 来告诉浏览器我们想要使用的web字体:

@font-face {
  font-family: 'Wotfard';
  src: url('/fonts/wotfard-regular.woff2') format('woff2');
  font-weight: 400;
  font-style: normal;
}
@font-face {
  font-family: 'Wotfard';
  src: url('/fonts/wotfard-medium.woff2') format('woff2');
  font-weight: 500;
  font-style: normal;
}
@font-face {
  font-family: 'Wotfard';
  src: url('/fonts/wotfard-bold.woff2') format('woff2');
  font-weight: 700;
  font-style: normal;
}
@font-face {
  font-family: 'Wotfard';
  src: url('/fonts/wotfard-regular-italic.woff2') format('woff2');
  font-weight: 400;
  font-style: italic;
}

我们需要多个语句,因为每个字体的weight和样式都有自己的文件。在本例中,我加载了3种不同的字体(常规字体、中等字体和粗体字体) ,以及一种用于常规字体的斜体变体。

字体文件本身不包含任何关于它的weight/样式的“元数据”,因此我们需要将它们链接起来。@font-face 语句中的所有声明需要做的。我们为一组字符指定源(src),然后指定与该字符集相关联的元数据(font-familyfont-weightfont-style)。

支持IE

在过去,我们需要为每个字符集提供3或4个字体文件,因为不同的浏览器支持不同的格式。

现在,事情变得好多了。.woff2格式是一种现代的、经过高度优化的格式,可以在所有主流浏览器上使用,但不适用于 IE。

如果你希望 IE 用户可以访问 web 字体,你需要指定一个.woff 格式的作为回退。

如果你使用 web 字体生成器或者下载了 web 字体包,你应该已经有了这个文件。他们通常会把你的字体转换成几种常见的格式。

@font-face 语句中,可以包含多个来源:

@font-face {
  font-family:'Wotfard';
  src: url('/fonts/wotfard-regular-italic.woff') format('woff'),
       url('/fonts/wotfard-regular-italic.woff2') format('woff2');
  font-weight: 400;
  font-style: normal;
}

一定要把它们按这个顺序排列,以便.woff2 排在最后; 浏览器将下载最终的可识别格式,我们希望现代浏览器使用.woff2(它倾向于生成较小的文件系统)。

当浏览器解析这些@font-face 语句时,将获取并加载 src 下声明的字体文件。你可以像使用任何字体一样使用 CSS 中的字体:

.something {
  font-family:'Wotfard';
  font-weight: 400;
}

伪粗体与斜体

@ font-face 语句允许我们将特定的字体权重值(比如700)连接到字符集。当我们使用font-weight: bold或font-weight: 700时,浏览器将使用粗体。

但是如果我们使用的字体没有相应的粗体字符集会发生什么呢?

浏览器会创建“为粗体”文本,它通过加粗每个字体中的线条来实现。这往往会让文字看上去有点模糊。可以看看下面字体中自带的粗体与浏览器生成的“伪粗体”的对比:

https://courses.joshwcomeau.com/course-materials/playfair-true-bold.png

真正的粗体(字体文件中自带的)

https://courses.joshwcomeau.com/course-materials/playfair-faux-bold.png

浏览器生成的“伪粗体”

当字体设计师创建字体的粗体变体时,他们会有意地和策略性地改变字符,优化美感和可读性。浏览器生成的伪粗体要简单粗暴得多,它们只是使线条更粗。

类似地,当提到斜体字时,浏览器只是简单地倾斜字母,而真正的斜体字使用替换字符:

https://courses.joshwcomeau.com/course-materials/playfair-true-italic.png

真正的斜体

https://courses.joshwcomeau.com/course-materials/playfair-faux-italic.png

浏览器生成的“伪斜体”

font-weight的四舍五入

现代浏览器在遇到其字体文件中不存在的font-weight值的字符集是,采用的“四舍五入”的策略。

假设我们决定从 web 字体中包含两种字体样式: 400 weight 和800 weight。

当你使用“bold”字体时会发生什么?

.thing {
	font-weight: bold;
}

或者,如果你设置的数值在字体文件中没有对应的字符集呢?

.thing {
	font-weight: 700;
}

在现代浏览器之前,精确度非常重要。这两种声明都会导致浏览器直接加粗 400 weight的字体而不是使用800 weight的字体。

而现代浏览器将使用800 weight的字体。