字体最佳实践

针对 Core Web Vitals 优化网页字体。

Katie Hempenius
Katie Hempenius

本文介绍了字体的性能最佳实践。网页字体影响性能的方式多种多样:

本文分为三部分:字体加载、字体分发和字体渲染。每个部分都介绍了字体生命周期的特定方面的运作方式,并提供了相应的最佳实践。

字体加载

字体通常是重要资源,因为如果没有字体,用户可能就无法查看网页内容。因此,字体加载的最佳实践通常侧重于确保尽早加载字体。应特别注意从第三方网站加载的字体,因为下载这些字体文件需要单独的连接设置。

如果您不确定系统是否及时请求了页面字体,请查看 Chrome 开发者工具中网络面板中的计时标签页,以了解更多信息。

开发者工具中的“时间”标签页的屏幕截图

了解 @font-face

在深入了解字体加载的最佳实践之前,请务必先了解 @font-face 的工作原理及其对字体加载的影响。

@font-face 声明是使用任何网页字体时不可或缺的一部分。它至少要声明将用于引用字体的名称,并指明相应字体文件的位置。

@font-face {
  font-family: "Open Sans";
  src: url("/fonts/OpenSans-Regular-webfont.woff2") format("woff2");
}

一个常见的误解是,在遇到 @font-face 声明时,系统会请求字体,但事实并非如此。@font-face 声明本身不会触发字体下载。相反,只有通过在网页上使用的样式引用某个字体时,系统才会下载该字体。例如:

@font-face {
  font-family: "Open Sans";
  src: url("/fonts/OpenSans-Regular-webfont.woff2") format("woff2");
}

h1 {
  font-family: "Open Sans"
}

换言之,在上面的示例中,仅当页面包含 <h1> 元素时,系统才会下载 Open Sans

因此,在考虑字体优化时,让样式表和字体文件本身一样重要,这一点非常重要。更改样式表的内容或提供方式会对字体的显示时间产生重大影响。同样,移除未使用的 CSS 和拆分样式表可以减少网页加载的字体数量。

内嵌字体声明

与将字体声明和其他关键样式内嵌在主文档的 <head> 中,而不是将它们添加到外部样式表中,大多数网站都会受益匪浅。这样一来,由于无需等待外部样式表下载,浏览器就能更快地发现字体声明。

<head>
  <style>
    @font-face {
        font-family: "Open Sans";
        src: url("/fonts/OpenSans-Regular-webfont.woff2") format("woff2");
    }

    body {
        font-family: "Open Sans";
    }

    ...etc.

  </style>
</head>

内嵌关键 CSS 是一种更高级的技术,并非所有网站都能实现。其性能优势很明显,但这需要额外的流程和构建工具,以确保正确内嵌必要的 CSS(最好是仅关键 CSS),并且任何其他 CSS 均以不阻塞渲染的方式进行分发。

预先连接到关键的第三方源

如果您的网站从第三方网站加载字体,强烈建议您使用 preconnect 资源提示尽早与第三方源建立连接。资源提示应放置在文档的 <head> 中。下面的资源提示设置了用于加载字体样式表的连接。

<head>
  <link rel="preconnect" href="https://fonts.com">
</head>

如需预先连接用于下载字体文件的连接,请添加一个单独的 preconnect 资源提示(使用 crossorigin 属性)。与样式表不同,字体文件必须通过 CORS 连接发送。

<head>
  <link rel="preconnect" href="https://fonts.com">
  <link rel="preconnect" href="https://fonts.com" crossorigin>
</head>

使用 preconnect 资源提示时,请注意字体提供程序可能会从不同的来源提供样式表和字体。例如,下面展示了如何将 preconnect 资源提示用于 Google Fonts。

<head>
  <link rel="preconnect" href="https://fonts.googleapis.com">
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
</head>

使用 preload 加载字体时要小心

虽然 preload 可以非常有效地在网页加载过程的早期发现字体,但这样做的代价是,加载其他资源时会导致浏览器资源减少。

内嵌字体声明和调整样式表可能是更有效的方法。这些调整更能解决字体延迟发现的根本原因,而不仅仅是提供一种解决方法。

此外,还应谨慎使用 preload 作为字体加载策略,因为它会绕过浏览器的某些内置内容协商策略。例如,preload 会忽略 unicode-range 声明,如果使用得当,应仅用于加载一种字体格式。

然而,在使用外部样式表时,预加载最重要的字体可能非常有效,因为浏览器要过一段时间才能发现是否需要相应字体。

字体传送

字体交付速度更快,文本渲染速度也更快。此外,如果提前提供字体,则有助于消除因字体交换而导致的布局偏移。

使用自托管的字体

从纸面上看,使用自托管字体应该可以带来更好的效果,因为它不需要第三方连接设置。但实际上,这两个选项之间的性能差异并不明显:例如,网页年鉴发现使用第三方字体的网站的渲染速度比使用第一方字体的字体快。

如果您考虑使用自托管字体,请确认您的网站使用的是内容分发网络 (CDN)HTTP/2。如果不使用这些技术,自托管字体显著提升性能的可能性会大大降低。如需了解详情,请参阅内容分发网络

如果您使用自托管字体,建议您同时应用第三方字体提供程序通常会自动提供的某些字体文件优化功能,例如字体子集设置和 WOFF2 压缩。应用这些优化措施所需的工作量在一定程度上取决于您的网站支持的语言。尤其要注意,优化 CJK 语言的字体可能特别具有挑战性。

使用 WOFF2

在所有新型字体字体中,WOFF2 是最新的字体,具有最广泛的浏览器支持,并提供最佳的压缩效果。由于 WOFF2 使用 Brotli,因此其压缩效果比 WOFF 高 30%,从而减少需要下载的数据,从而提升性能。

鉴于浏览器支持,专家现在建议仅使用 WOFF2:

事实上,我们认为现在也是时候宣布:仅使用 WOFF2,不用操心其他一切。

这将大幅简化您的 CSS 和工作流程,并防止意外下载双重或不正确的字体。现在在所有位置都支持 WOFF2。因此,除非您需要支持非常旧的浏览器,否则只需使用 WOFF2 即可。如果无法投放,请考虑不再向这些旧版浏览器提供任何网络字体。如果您具备可靠的后备策略,这不会成为问题。使用旧版浏览器的访问者只能看到后备字体。

Bram Stein,2022 年网络年鉴

子集内嵌字体

字体文件通常包含针对其支持的所有各种字符的大量字形。但您可能并不需要网页上的所有字符,可以通过设置字体子集来减小字体文件的大小。

@font-face 声明中的 unicode-range 描述符会告知浏览器某种字体可用于哪些字符。

@font-face {
    font-family: "Open Sans";
    src: url("/fonts/OpenSans-Regular-webfont.woff2") format("woff2");
    unicode-range: U+0025-00FF;
}

如果网页包含一个或多个与 Unicode 范围匹配的字符,系统将会下载字体文件。unicode-range 通常用于提供不同的字体文件,具体取决于网页内容所使用的语言。

unicode-range 通常与子集内嵌技术结合使用。子集字体包含原始字体文件中包含的一小部分字形。例如,网站可能会为拉丁字符和西里尔字符生成单独的子集字体,而不是向所有用户提供所有字符。每种字体的字形数量差异很大:每种字体的字形数量通常为 100 到 1,000 个;CJK 字体可能有超过 1 万个字符。移除未使用的字形可以显著减小字体的文件大小。

某些字体提供程序可能会自动提供具有不同子集的不同版本的字体文件。例如,Google Fonts 默认会执行此操作:

/* devanagari */
@font-face {
  font-family: 'Poppins';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url(https://fonts.gstatic.com/s/poppins/v20/pxiEyp8kv8JHgFVrJJbecnFHGPezSQ.woff2) format('woff2');
  unicode-range: U+0900-097F, U+1CD0-1CF6, U+1CF8-1CF9, U+200C-200D, U+20A8, U+20B9, U+25CC, U+A830-A839, U+A8E0-A8FB;
}
/* latin-ext */
@font-face {
  font-family: 'Poppins';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url(https://fonts.gstatic.com/s/poppins/v20/pxiEyp8kv8JHgFVrJJnecnFHGPezSQ.woff2) format('woff2');
  unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
  font-family: 'Poppins';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url(https://fonts.gstatic.com/s/poppins/v20/pxiEyp8kv8JHgFVrJJfecnFHGPc.woff2) format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

改用自托管后,这种优化很容易被忽略,并会导致在本地产生更大的字体文件。

如果您的字体提供程序允许,您还可以通过以下两种方式手动对字体进行子集内嵌:通过 API(Google Fonts 通过提供 text 参数来支持此功能),或手动修改字体文件,然后自行托管。用于生成字体子集的工具包括子字体字形。不过,请务必检查您所使用的字体的许可,即允许子集内嵌和自托管。

减少使用网页字体

加载速度最快的字体是一开始无需请求的字体。系统字体和可变字体是可能减少网站使用网页字体的两种方法。

系统字体是用户设备界面使用的默认字体。系统字体通常因操作系统和版本而异。由于已安装字体,因此无需下载。系统字体非常适合正文文本。

如需在 CSS 中使用系统字体,请将 system-ui 列为字体系列:

font-family: system-ui

可变字体背后的理念是用一个可变字体替代多个字体文件。可变字体的工作原理是定义“默认”字体样式并提供用于操控字体的“轴”。例如,具有 Weight 轴的可变字体可用于实现以前需要为浅色、常规、粗体和超粗体使用单独的字体的字母。

并非每个人都能从改用可变字体中受益。可变字体包含多种样式,因此文件大小通常大于只包含一种样式的各个不可变字体。使用(并且需要使用)多种字体样式和粗细的网站,如果使用可变字体,效果会非常显著。

字体渲染

当面临尚未加载的网络字体时,浏览器将面临一个困境:是否应该在网络字体到达之前暂停渲染文本?还是应该在网页字体到达之前以后备字体呈现文本?

不同浏览器处理这种情况的方式有所不同。默认情况下,如果关联的网页字体尚未加载,基于 Chromium 和 Firefox 的浏览器将会阻止文本呈现,最长为 3 秒钟;Safari 将无限期地阻止文本呈现。

您可以使用 font-display 属性来配置此行为。此选择可能会产生重大影响:font-display 可能会影响 LCP、FCP 和布局稳定性。

选择合适的font-display策略

font-display 会告知浏览器在关联的网页字体尚未加载时应如何继续进行文本渲染。它是按字体定义的。

@font-face {
  font-family: Roboto, Sans-Serif
  src: url(/fonts/roboto.woff) format('woff'),
  font-display: swap;
}

font-display 有五个可能的值:

屏蔽期 替换期
自动选择 因浏览器而异 因浏览器而异
屏蔽 2-3 秒 无限
交换 0 毫秒 无限
后备 100 毫秒 3 秒
选填 100 毫秒
  • 屏蔽期:屏蔽期从浏览器请求网页字体时开始计算。在屏蔽期间,如果网页字体不可用,相应字体将以不可见的后备字体呈现,因此用户将无法看到相应文本。如果在屏蔽期结束时相应字体不可用,系统会以后备字体呈现。
  • 交换期:交换期在屏蔽期之后。如果网页字体在交换期内可用,则会被“交换”。

font-display 策略反映了关于性能和美观之间权衡的不同观点。因此,我们很难给出推荐方法,因为这取决于个人偏好、网络字体对页面和品牌的重要性,以及换入新字体后迟到的字体会有多不刺眼。

对于大多数网站,以下三种策略最为适用:

  • 如果您优先考虑性能:请使用 font-display: optional。这是最“性能”的方法:文本渲染的延迟不超过 100 毫秒,并可以保证不会发生与字体交换相关的布局偏移。但这种方法的缺点是,如果网页字体出现延迟,将无法使用网页字体。

  • 如果快速显示文本是首要任务,但您仍想确保使用网页字体:请使用 font-display: swap,但请务必尽早提供字体,以免导致布局偏移。这种方法的缺点是字体延迟出现时会出现突兀的偏移。

  • 如果确保文本以网页字体显示是首要任务:请使用 font-display: block,但请务必尽早提供字体,以最大限度地缩短文本延迟。这样做的缺点是初始文本显示会延迟。请注意,尽管如此,它仍可能导致布局偏移,因为文本实际上绘制为不可见,因此后备字体空间被用于保留空间。网页字体加载后,可能需要差异空间,因此需要偏移。不过,这种偏移不会比 font-display: swap 复杂,因为文本本身不会发生偏移。

另请注意,您可以结合使用这两种方法:例如,对品牌信息及其他具有视觉吸引力的网页元素使用 font-display: swap;对正文文本中使用的字体使用 font-display: optional

减少后备字体与网页字体之间的偏移

为了降低 CLS 影响,您可以使用新的 size-adjust 属性。如需了解详情,请参阅关于 CSS size-adjust 的文章。这是我们工具集中的新增版本,因此目前更为先进,需要手动完成一些操作。不过,它绝对值得大家进行实验并留意未来工具方面的改进!

总结

网络字体仍然是性能瓶颈,但我们有越来越多的选项允许对它们进行优化,以尽可能减少这个瓶颈。