Zach Leatherman 撰写了一篇 关于字体加载策略的综合列表,该列表在 Web 开发领域被广泛分享。我之前看过这份列表,但感到非常害怕(和困惑),以至于我决定什么也不做。我不知道如何以最佳方式开始加载字体,也不想在浏览这份列表时感觉自己像个傻瓜。
今天,我给自己留出时间 坐下来弄清楚。在这篇文章中,我想与您分享最佳加载策略以及如何执行所有这些策略。
最佳策略
Zach 在他的文章中推荐了两种策略
- 带有类的 FOUT – 大多数情况下的最佳方法。(无论我们使用字体托管公司还是自己托管字体,这都适用。)
- 关键 FOFT – 性能最佳的方法。(这仅在我们自己托管字体时有效。)
在深入探讨这两种策略之前,我们需要先澄清这些缩写词,以便您了解 Zach 的意思。
FOIT、FOUT、FOFT
缩写词如下
- FOIT 表示不可见文本闪烁。当 Web 字体加载时,我们会隐藏文本,以便用户看不到任何内容。我们仅在 Web 字体加载后显示文本。
- FOUT 表示未设置样式的文本闪烁。当 Web 字体加载时,我们会向用户显示系统字体。当 Web 字体加载完成后,我们会将文本更改回所需的 Web 字体。
- FOFT 表示伪文本闪烁。这个比较复杂,需要更多解释。当我们进入 FOFT 部分时,我们将详细讨论它。
自托管字体与云托管字体
有两种主要方法可以托管字体
- 使用云提供商。
- 自托管字体。
我们加载字体的方式在很大程度上取决于您选择的选项。
使用云托管字体加载字体
使用云托管字体通常更容易。提供商将提供一个加载字体的链接。我们可以简单地将此链接放入我们的 HTML 中,然后就可以获取我们的 Web 字体。以下是一个示例,我们现在从 Adobe Fonts(以前称为 Typekit)加载 Web 字体。
<link rel="stylesheet" href="https://use.typekit.net/your-kit-id.css">
不幸的是,这不是最佳方法。href
会阻塞浏览器。如果加载 Web 字体挂起,用户将无法执行任何操作,只能等待。
Typekit 曾经提供过异步加载字体的 JavaScript。很遗憾,他们不再显示此 JavaScript 版本。(代码仍然有效,但我不知道何时或是否会停止工作。)
从 Google Fonts 加载字体稍微好一点,因为它提供了font-display: swap
。以下是一个从 Google Fonts 加载 Lato 的示例。(display=swap
参数会触发font-display: swap
)。
<link
href="https://fonts.googleapis.com/css2?family=Lato:ital,wght@0,400;0,700;1,400;1,700&display=swap"
rel="stylesheet"
/>
使用自托管字体加载字体
只有在您拥有许可证的情况下才能自托管您的字体。由于字体可能很昂贵,因此大多数人选择使用云提供商。
不过,有一种获取字体的廉价方法。Design Cuts 偶尔会进行字体促销活动,您可以以每个捆绑包仅 29 美元的价格获得质量极高的字体。每个捆绑包最多可以包含 12 种字体。我设法获得了 Claredon、DIN、Futura 等经典字体,以及一大堆可以玩的字体,方法是在他们的时事通讯上蹲点等待这些促销活动。
如果我们想自托管字体,我们需要了解另外两个概念:@font-face
和 font-display: swap
。
@font-face
@font-face
允许我们在 CSS 中声明字体。如果我们想自托管字体,我们需要使用@font-face
来声明我们的字体。
在此声明中,我们可以指定四件事
font-family
:这告诉 CSS(和 JavaScript)我们字体的名称。src
:查找字体的路径,以便它们可以加载font-weight
:字体粗细。如果省略,则默认为 400。font-style
:是否要使字体斜体。如果省略,则默认为 normal。
对于src
,我们需要指定几种字体格式。为了 获得实用的浏览器支持水平,我们可以使用woff2
和woff
。
以下是一个通过@font-face
加载 Lato 的示例。
@font-face {
font-family: Lato;
src: url('path-to-lato.woff2') format('woff2'),
url('path-to-lato.woff') format('woff');
}
@font-face {
font-family: Lato;
src: url('path-to-lato-bold.woff2') format('woff2'),
url('path-to-lato-bold.woff') format('woff');
font-weight: 700;
}
@font-face {
font-family: Lato;
src: url('path-to-lato-italic.woff2') format('woff2'),
url('path-to-lato-italic.woff') format('woff');
font-style: italic;
}
@font-face {
font-family: Lato;
src: url('path-to-lato-bold-italic.woff2') format('woff2'),
url('path-to-lato-bold-italic.woff') format('woff');
font-weight: 700;
font-style: italic;
}
如果您没有woff2
或woff
文件,您可以将字体文件(Opentype 或 Truetype)上传到 字体生成器 以获取它们。
接下来,我们在font-family
属性中声明 Web 字体。
html {
font-family: Lato, sans-serif;
}
当浏览器解析带有 Web 字体的元素时,它们会触发 Web 字体的下载。
font-display: swap
font-display
可以取四个可能的值之一:auto
、swap
、fallback
和optional
。swap
指示浏览器在 Web 字体加载之前显示回退文本。换句话说,swap
会为自托管字体触发 FOUT。您可以在 CSS-Tricks 年鉴 中了解其他值。
font-display: swap
的浏览器支持现在相当不错,因此我们可以在项目中使用它。
此浏览器支持数据来自 Caniuse,其中包含更多详细信息。数字表示浏览器从该版本开始支持该功能。
桌面
Chrome | Firefox | IE | Edge | Safari |
---|---|---|---|---|
60 | 58 | 否 | 79 | 11.1 |
移动/平板电脑
Android Chrome | Android Firefox | Android | iOS Safari |
---|---|---|---|
127 | 127 | 127 | 11.3-11.4 |
FOUT 与带有类的 FOUT
FOUT 表示未设置样式的文本闪烁。您始终希望 FOUT 优于 FOIT(不可见文本闪烁),因为与使用隐形墨水编写的文字相比,用户更容易阅读用系统字体编写的文字。我们之前提到过,font-display: swap
使您能够本地使用 FOUT。
带有类的 FOUT 会为您提供相同的结果——FOUT——但它使用 JavaScript 加载字体。其机制如下
- 首先:加载系统字体。
- 其次:通过 JavaScript 加载 Web 字体。
- 第三:加载 Web 字体后,向
<html>
标签添加一个类。 - 第四:用加载的 Web 字体替换系统字体。
尽管font-display: swap
拥有良好的浏览器支持,但 Zach 建议通过 JavaScript 加载字体。换句话说,Zach 建议使用带有类的 FOUT 而不是@font-face
+ font-display: swap
。
他推荐使用带有类的 FOUT 是基于以下三个原因
- 我们可以对重绘进行分组。
- 我们可以适应用户偏好。
- 如果用户连接缓慢,我们可以完全跳过字体加载。
我将让您在 另一篇文章 中更深入地了解这些原因。在撰写本文时,我发现了一个偏好带有类的 FOUT 的第四个原因:当用户已经在其系统中加载了字体时,我们可以跳过字体加载。(我们使用sessionStorage
来做到这一点,我们将在下面看到。)
使用类进行 FOUT(适用于云托管字体)
首先,我们像往常一样从您的云托管公司加载字体。以下是一个我从 Google Fonts 加载 Lato 的示例。
<head>
<link
href="https://fonts.googleapis.com/css2?family=Lato:ital,wght@0,400;0,700;1,400;1,700&display=swap"
rel="stylesheet"
/>
</head>
接下来,我们想通过 JavaScript 加载字体。由于代码占用空间小,并且无论如何都是异步的,因此我们将把一个 script
注入到 <head>
部分。
<head>
<link
href="https://fonts.googleapis.com/css2?family=Lato:ital,wght@0,400;0,700;1,400;1,700&display=swap"
rel="stylesheet"
/>
<script src="js/load-fonts.js"></script>
</head>
在 load-fonts.js
中,我们想使用 CSS 字体加载 API 来加载字体。在这里,我们可以使用 Promise.all
来同时加载所有字体。这样做时,我们正在对重绘进行分组。
代码如下所示
if ('fonts' in document) {
Promise.all([
document.fonts.load('1em Lato'),
document.fonts.load('700 1em Lato'),
document.fonts.load('italic 1em Lato'),
document.fonts.load('italic 700 1em Lato')
]).then(_ => () {
document.documentElement.classList.add('fonts-loaded')
})
}
加载字体后,我们希望将正文文本更改为网页字体。我们可以通过使用 .fonts-loaded
类在 CSS 中执行此操作。
/* System font before [[webfont]]s get loaded */
body {
font-family: sans-serif;
}
/* Use [[webfont]] when [[webfont]] gets loaded*/
.fonts-loaded body {
font-family: Lato, sans-serif;
}
请注意:使用此方法需要使用 .fonts-loaded
类。
我们不能直接在 <body>
的 font-family
中编写网页字体;这样做会触发字体下载,这意味着您正在使用 @font-face + font-display: swap
。这也意味着 JavaScript 是多余的。
/* DO NOT DO THIS */
body {
font-family: Lato, sans-serif;
}
如果用户访问网站上的其他页面,他们的浏览器中已经加载了字体。我们可以跳过字体加载过程(以加快速度),方法是使用 sessionStorage
。
if (sessionStorage.fontsLoaded) {
document.documentElement.classList.add('fonts-loaded')
} else {
if ('fonts' in document) {
Promise.all([
document.fonts.load('1em Lato'),
document.fonts.load('700 1em Lato'),
document.fonts.load('italic 1em Lato'),
document.fonts.load('italic 700 1em Lato')
]).then(_ => () {
document.documentElement.classList.add('fonts-loaded')
})
}
}
如果我们想优化 JavaScript 的可读性,可以使用早期返回模式来减少缩进。
function loadFonts () {
if (sessionStorage.fontsLoaded) {
document.documentElement.classList.add('fonts-loaded')
return
}
if ('fonts' in document) {
Promise.all([
document.fonts.load('1em Lato'),
document.fonts.load('700 1em Lato'),
document.fonts.load('italic 1em Lato'),
document.fonts.load('italic 700 1em Lato')
]).then(_ => () {
document.documentElement.classList.add('fonts-loaded')
})
}
loadFonts()
使用类进行 FOUT(适用于自托管字体)
首先,我们想通过 @font-face
像往常一样加载字体。由于我们通过 JavaScript 加载字体,因此 font-display: swap
属性是可选的。
@font-face {
font-family: Lato;
src: url('path-to-lato.woff2') format('woff2'),
url('path-to-lato.woff') format('woff');
}
/* Other @font-face declarations */
接下来,我们通过 JavaScript 加载网页字体。
if ('fonts' in document) {
Promise.all([
document.fonts.load('1em Lato'),
document.fonts.load('700 1em Lato'),
document.fonts.load('italic 1em Lato'),
document.fonts.load('italic 700 1em Lato')
]).then(_ => () {
document.documentElement.classList.add('fonts-loaded')
})
}
然后,我们想通过 CSS 将正文文本更改为网页字体。
/* System font before webfont is loaded */
body {
font-family: sans-serif;
}
/* Use webfont when it loads */
.fonts-loaded body {
font-family: Lato, sans-serif;
}
最后,我们跳过重复访问的字体加载。
if ('fonts' in document) {
Promise.all([
document.fonts.load('1em Lato'),
document.fonts.load('700 1em Lato'),
document.fonts.load('italic 1em Lato'),
document.fonts.load('italic 700 1em Lato')
]).then(_ => () {
document.documentElement.classList.add('fonts-loaded')
})
}
FontFaceObserver
CSS 字体加载器 API 与 CSS 字体加载器 API 拥有相当不错的浏览器支持,但它是一个相当难用的 API。
此浏览器支持数据来自 Caniuse,其中包含更多详细信息。数字表示浏览器从该版本及更高版本开始支持该功能。
桌面
Chrome | Firefox | IE | Edge | Safari |
---|---|---|---|---|
35 | 41 | 否 | 79 | 10 |
移动/平板电脑
Android Chrome | Android Firefox | Android | iOS Safari |
---|---|---|---|
127 | 127 | 127 | 10.0-10.2 |
因此,如果您需要支持旧版浏览器(如 IE 11 及以下版本),或者您发现此 API 奇怪且难以使用,则可能需要使用 Bramstein 的 FontFaceObserver
。它非常轻量级,因此没有太大害处。
代码如下所示。(与 CSS 字体加载器 API 相比,它好得多)。
new FontFaceObserver('lato')
.load()
.then(_ => {
document.documentElement.classList.add('fonts-loaded')
})
如果您打算让它在不支持 Promise 的浏览器上加载字体,请确保使用 fontfaceobserver.standalone.js
。
FOFT
FOFT 表示伪文本闪现。这里的想法是我们将字体加载分为三个阶段
- 步骤 1:当网页字体尚未加载时,使用回退字体。
- 步骤 2:首先加载字体的罗马体(也称为“书籍”或“常规”)版本。这将替换大部分文本。粗体和斜体将由浏览器伪造(因此称为“伪文本”)。
- 步骤 3:加载其余字体
注意:Zach 也称之为标准 FOFT。
使用标准 FOFT
首先,我们加载罗马体字体。
@font-face {
font-family: LatoInitial;
src: url('path-to-lato.woff2') format('woff2'),
url('path-to-lato.woff') format('woff');
unicode-range: U+65-90, U+97-122;
}
.fonts-loaded-1 body {
font-family: LatoInitial;
}
if('fonts' in document) {
document.fonts.load("1em LatoInitial")
.then(_ => {
document.documentElement.classList.add('fonts-loaded-1')
})
}
然后,我们加载其他字体。
请注意:我们再次加载 Lato
,但这次我们将 font-family
设置为 Lato
而不是 LatoInitial
。
/* Load this first */
@font-face {
font-family: LatoInitial;
src: url('path-to-lato.woff2') format('woff2'),
url('path-to-lato.woff') format('woff');
unicode-range: U+65-90, U+97-122;
}
/* Load these afterwards */
@font-face {
font-family: Lato;
src: url('path-to-lato.woff2') format('woff2'),
url('path-to-lato.woff') format('woff');
unicode-range: U+65-90, U+97-122;
}
/* Other @font-face for different weights and styles*/
.fonts-loaded-1 body {
font-family: LatoInitial;
}
.fonts-loaded-2 body {
font-family: Lato;
}
if ('fonts' in document) {
document.fonts.load('1em LatoInitial')
.then(_ => {
document.documentElement.classList.add('fonts-loaded-1')
Promise.all([
document.fonts.load('400 1em Lato'),
document.fonts.load('700 1em Lato'),
document.fonts.load('italic 1em Lato'),
document.fonts.load('italic 700 1em Lato')
]).then(_ => {
document.documentElement.classList.add('fonts-loaded-2')
})
})
}
同样,我们可以针对重复查看对其进行优化。
在这里,我们可以直接在 <html>
中添加 fonts-loaded-2
,因为字体已加载。
function loadFonts () {
if (sessionStorage.fontsLoaded) {
document.documentElement.classList.add('fonts-loaded-2')
return
}
if ('fonts' in document) {
document.fonts.load('1em Lato')
.then(_ => {
document.documentElement.classList.add('fonts-loaded-1')
Promise.all([
document.fonts.load('400 1em Lato'),
document.fonts.load('700 1em Lato'),
document.fonts.load('italic 1em Lato'),
document.fonts.load('italic 700 1em Lato')
]).then(_ => {
document.documentElement.classList.add('fonts-loaded-2')
// Optimization for Repeat Views
sessionStorage.fontsLoaded = true
})
})
}
}
关键 FOFT
“关键”部分来自“关键 CSS”(我相信)——我们只在加载其余内容之前加载必要的 CSS。我们这样做是因为它提高了性能。
在排版方面,唯一关键的是字母 A 到 Z(大写和小写字母)。我们可以使用 unicode-range
创建这些字体的子集。
创建此子集时,我们还可以使用必要的字符创建单独的字体文件。
以下是 @font-face
声明的样子
@font-face {
font-family: LatoSubset;
src: url('path-to-optimized-lato.woff2') format('woff2'),
url('path-to-optimized-lato.woff') format('woff');
unicode-range: U+65-90, U+97-122;
}
我们首先加载此子集。
.fonts-loaded-1 body {
font-family: LatoSubset;
}
if('fonts' in document) {
document.fonts.load('1em LatoSubset')
.then(_ => {
document.documentElement.classList.add('fonts-loaded-1')
})
}
然后我们稍后加载其他字体文件。
.fonts-loaded-2 body {
font-family: Lato;
}
if ('fonts' in document) {
document.fonts.load('1em LatoSubset')
.then(_ => {
document.documentElement.classList.add('fonts-loaded-1')
Promise.all([
document.fonts.load('400 1em Lato'),
document.fonts.load('700 1em Lato'),
document.fonts.load('italic 1em Lato'),
document.fonts.load('italic 700 1em Lato')
]).then(_ => {
document.documentElement.classList.add('fonts-loaded-2')
})
})
}
同样,我们可以针对重复查看对其进行优化
function loadFonts () {
if (sessionStorage.fontsLoaded) {
document.documentElement.classList.add('fonts-loaded-2')
return
}
if ('fonts' in document) {
document.fonts.load('1em LatoSubset')
.then(_ => {
document.documentElement.classList.add('fonts-loaded-1')
Promise.all([
document.fonts.load('400 1em Lato'),
document.fonts.load('700 1em Lato'),
document.fonts.load('italic 1em Lato'),
document.fonts.load('italic 700 1em Lato')
]).then(_ => {
document.documentElement.classList.add('fonts-loaded-2')
// Optimization for Repeat Views
sessionStorage.fontsLoaded = true
})
})
}
}
关键 FOFT 变体
Zach 提出了另外两种关键 FOFT 变体
- 使用 Data URI 的关键 FOFT
- 使用预加载的关键 FOFT
使用数据 URI 的关键 FOFT
在此变体中,我们使用数据 URI 而不是普通链接来加载关键字体(子集化的罗马体字体)。以下是它的样子。(而且看起来很吓人)。
@font-face {
font-family: LatoSubset;
src: url("data:application/x-font-woff;charset=utf-8;base64,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") format("woff");
/* ... */
}
此代码看起来很吓人,但无需害怕。这里最困难的部分是生成数据 URI,并且 CSS-Tricks 在这里为我们提供了帮助。
使用预加载的关键 FOFT
在此变体中,我们使用带有 preload
标记的链接添加到关键字体文件。以下是它的样子
<link rel="preload" href="path-to-roman-subset.woff2" as="font" type="font/woff2" crossorigin>
我们应该只预加载一个字体。如果我们加载多个,可能会对初始加载时间产生不利影响。
在 策略列表中,Zach 还提到他更喜欢使用数据 URI 而不是 preload
变体。他之所以更喜欢它,是因为 preload
的浏览器支持过去很糟糕。今天,我认为浏览器的支持已经足够好,可以选择预加载而不是数据 URI。
此浏览器支持数据来自 Caniuse,其中包含更多详细信息。数字表示浏览器从该版本及更高版本开始支持该功能。
桌面
Chrome | Firefox | IE | Edge | Safari |
---|---|---|---|---|
50 | 85 | 否 | 79 | 11.1 |
移动/平板电脑
Android Chrome | Android Firefox | Android | iOS Safari |
---|---|---|---|
127 | 127 | 127 | 11.3-11.4 |
Zach 的最后说明
Chris 通过 Zach 运行了这篇文章,Zach 希望他在他的 原始文章中优先考虑无 JavaScript 的方法。
我认为这篇文章很好,但我认为它所基于的文章在某些方面可能有点过时了。我希望当您只使用一个或两个字体文件(或更多,但每个字体类型仅 1 或 2 个)时,它能更优先考虑无 JS 方法。我认为 JS 方法现在有点像例外情况(除非您使用大量字体文件或云提供商不支持
font-display: swap
)。
这稍微改变了结论,我将在下一节中进行总结。
使用哪种字体加载策略?
如果您使用云托管提供商
- 如果主机提供,请使用
font-display: swap
。 - 否则,使用使用类进行 FOUT。
如果您托管您的网页字体,您有几个选择
@font-face
+font-display: swap
- 带类名的FOUT
- 标准FOFT
- 关键FOFT
如何选择它们
- 选择
@font-face
+font-display: swap
,如果你刚开始接触,并且不想使用JavaScript。这是最简单的一种方法。如果你每个字体只使用少量字体文件(少于两个文件),也选择此选项。 - 选择标准FOFT,如果你准备使用JavaScript,但不想额外做罗马字体的子集化工作。
- 选择关键FOFT变体,如果你想为了性能做到极致。
就是这样!希望你发现所有这些内容都有用!
如果你喜欢这篇文章,你可能会喜欢我写的其他文章。考虑订阅我的时事通讯😉。此外,如果你有任何问题,请随时联系我。我会尽力帮助你!
关于带类名FOUT的建议,我有一些反驳意见。
1. 在重绘方面,有点吹毛求疵,但当然,字体的加载速度会有所不同,所以我同意这一点。
2和3. 用户偏好/缓慢连接:这可以通过
prefers-reduced-data
媒体查询来解决(目前浏览器支持率较低)。关于加载本地字体的第四点可以通过在你的
font-face
声明中使用“local”属性来解决。例如这些建议基本上是在某种程度上与浏览器对抗,这可能没有必要。
我不是说你不应该做这些事情——毕竟文章的重点是你可以从字体加载中获得的最佳性能。我的观点更多地是从开发者体验的角度出发。
你忘记在加载字体时实际设置
sessionStorage.fontsLoaded
了……此外,LatoInitial和Lato的定义相同,所以我不确定为什么要在它们之间切换。
最后,我自己更喜欢
font-display: swap
。其中的任何错误都是浏览器需要修复的,而不是我,而且它无需JS即可工作——而你的其他策略则都需要。感谢你指出缺少的
sessionStorage.setItem("fontsLoaded", 1);
语句。我很好奇它是如何设置的!使用JS加载可以加快字体加载速度,因为它可以在DOM加载之前开始,而
font-display: swap
则会在之后加载。这确保了字体不会在DOM中未使用时加载。但我认为一个无JS版本会很棒(例如
font-display: swap eager
?),我同意。Sora关于重复字体声明的说法是正确的。如果你查看Zach Leatherman的演示代码,他并没有这样做。
顺便说一句,我编写了一个WordPress插件来帮助实现Zach的首选字体加载方案。如果你想试用,可以在插件库中搜索“WP FOFT Loader”。
感谢这份总结——多年来我一直关注着Zach优秀的博文以及所有更新,我很高兴Font Loading API现在已经足够好,可以满足大多数用途(但传统的FontFaceObserver和其他JS解决方法仍然可以用于修复需要更好性能或支持的地方)。这是一个如此令人困惑的话题!
我还希望当可变字体得到更广泛的应用时,其中一些技术会变得不那么重要。提供一个优化的网络字体比为每个变体排队下载单独的字体要好得多。
正如Sora2455已经指出的那样,这篇文章中的代码示例存在一些小错误,如果按原样编写,将导致它们实际上毫无用处。
所有带有
sessionStorage.fontsLoaded
的示例都需要在then()
方法中将该属性实际设置为true
,否则这些条件永远不会被命中,字体将在每个后续页面加载时继续加载。(我曾在生产站点上不小心这样做过,并且有一段时间没有注意到!哎!)使用
LatoInitial
和Lato
的标准FOFT示例只有在为其他权重和样式添加了额外的Lato
引用时才有意义——也许可以将这些添加到示例中,以便该部分具有可理解的目的?此外,可能值得一提的是,许多网络字体许可证禁止修改,例如子集化,甚至可能禁止从铸造厂提供的格式转换(例如,使用FontSquirrel或
fonttools
将.otf转换为.woff2),这可能需要根据读者想要做什么来考虑……如果你使用“带类名的FOUT”方法,是否也有意义同时预加载字体?
有没有人成功减少了使用Typekit字体和
display: swap
引起的CLS?正如文章中提到的,Adobe现在建议使用他们的css“方法”,因此不再有“wf-loaded”类可以使用了。我已经启用了display: swap
选项,但当然,回退字体和已加载字体的行高不同,因此CLS总是会出现。只是为了未来的访问者修复了2个错别字。