字体最佳实践

针对核心 Web 指标优化 Web 字体。

本文档介绍了字体的性能最佳实践。网络字体会通过多种方式影响性能:

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

字体加载

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

如果您不确定是否及时请求了网页的字体,请查看 Chrome DevTools 中 Network 面板内的 Timing 标签页,了解详情。

DevTools 中的 Timing 标签页。

了解 @font-face

在深入了解字体加载最佳实践之前,请务必了解 @font-face 的运作方式以及这对字体加载有何影响。

@font-face 声明是使用任何 Web 字体的必要部分。它至少声明了用于引用字体的名称,并指明了相应字体文件的位置。

@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>

如需预连接用于下载字体文件的连接,请添加使用 crossorigin 属性的单独 preconnect 资源提示。与样式表不同,字体文件必须通过 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 声明,如果谨慎使用,则应仅用于加载单个字体格式。

不过,使用外部样式表时,预加载最重要的字体会非常有效,因为否则浏览器要等到很久以后才会发现是否需要该字体。

字体提交

字体传送速度越快,文本呈现速度就越快。此外,如果提交字体的时间足够早,有助于消除因字体切换而导致的布局偏移。

使用自托管字体

从理论上讲,使用自托管字体应该可以提供更好的性能,因为它可以消除第三方连接设置。在实践中,这两种方法之间的性能差异并不那么明显。例如,Web Almanac 发现,使用第三方字体的网站的呈现速度比使用第一方字体的网站更快。

如果您考虑使用自托管字体,请确认您的网站使用了内容传送网络 (CDN)HTTP/2。如果不使用这些技术,自托管字体很难提供更好的性能。

如果您使用的是自托管的字体,建议您还应用第三方字体提供商通常自动提供的一些字体文件优化。例如,字体子集和 WOFF2 压缩。应用这些优化所需的工作量在一定程度上取决于您的网站支持的语言。请务必注意,针对 CJK 语言优化字体可能特别具有挑战性。

使用 WOFF2

在现代字体中,WOFF2 是最新的,支持浏览器最多,压缩率最高。由于使用 Brotli,WOFF2 的压缩率比 WOFF 高 30%,因此需要下载的数据更少,性能也更快。

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

事实上,我们认为现在也该宣告:请只使用 WOFF2,忘记所有其他格式。

这样可以极大地简化 CSS 和工作流,还能防止意外下载重复或错误的字体。现在,所有平台都支持 WOFF2。因此,除非您需要支持非常古老的浏览器,否则请只使用 WOFF2。如果无法做到,不妨考虑完全不向这些旧版浏览器提供任何 Web 字体。如果您制定了可靠的回退策略,就不会出现此问题。使用旧版浏览器的访问者会看到您的后备字体。

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 字体可能包含超过 10,000 个字符。

移除未使用的字形可以显著缩减字体的文件大小。

某些字体提供商可能会自动提供不同版本的字体文件,其中包含不同的子集。例如,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 参数支持此操作),或手动修改字体文件,然后自行托管。用于生成字体子集的工具包括 subfontglyphanger

请务必检查字体许可,确认它们是否允许子集和自托管。

使用更少的 Web 字体

最快可传送的字体是最初未请求的字体。系统字体和可变字体是可能减少您网站上使用的 Web 字体数量的两种方式。

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

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

font-family: system-ui

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

并非所有用户都适合改用可变字体。 可变字体包含许多样式,因此通常比仅包含一种样式的单个非可变字体的文件大小更大。使用可变字体后效果提升最明显的网站是那些使用(并需要使用)各种字体样式和粗细的网站。

字体渲染

当遇到尚未加载的 Web 字体时,浏览器会面临一个两难选择:在 Web 字体到达之前,是否应暂停渲染文本?或者,在 Web 字体到达之前,应使用回退字体渲染文本?

不同的浏览器会以不同的方式处理此场景。默认情况下,如果未加载关联的 Web 字体,基于 Chromium 的浏览器和 Firefox 浏览器会阻止文本渲染最多 3 秒。Safari 会无限期阻止文本渲染。

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

选择适当的 font-display 策略

font-display 用于告知浏览器在未加载关联的 Web 字体时应如何继续渲染文本。它是按字体定义的。

@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 毫秒
  • 屏蔽期:屏蔽期从浏览器请求 Web 字体开始。在屏蔽期间,如果 Web 字体不可用,系统会使用不可见后备字体渲染该字体,因此用户看不到文本。如果字体在屏蔽期结束时仍不可用,则系统会使用回退字体渲染该字体。
  • 交换期:交换期紧随阻止期之后。如果网页字体在交换期内可用,系统会将其“换入”。

font-display 策略反映了人们对效果和美感之间权衡的不同观点。因此,很难推荐一种方法,因为这取决于个人偏好、Web 字体对网页和品牌的重要性,以及延迟到达的字体换入时可能造成的震撼程度。

对于大多数网站,以下是根据您的首要任务最适合的三种策略:

  • 性能:使用 font-display: optional。这是最“高效”的方法:文本渲染延迟不超过 100 毫秒,并且可以保证不会出现与字体切换相关的布局偏移。缺点是,如果 Web 字体送达较晚,系统将不会使用该字体。

  • 快速显示文本并仍使用 Web 字体:使用 font-display: swap,但请务必尽早提交字体,以免导致布局偏移。此选项的缺点是,当字体延迟送达时,会出现突兀的转换。

  • 文本以 Web 字体显示:使用 font-display: block,但请务必尽早提交字体,以最大限度地减少文本延迟。初始文本显示会延迟。尽管存在这种延迟,但仍可能会导致布局偏移,因为文本实际上是不可见的,因此回退字体空间会用于预留空间。网页字体加载后,这可能需要不同的空间,因此会发生偏移。与 font-display: swap 相比,这种转换可能不会那么突兀,因为文本本身不会发生转换。

另请注意,这两种方法可以结合使用:例如,使用 font-display: swap 来设置品牌元素和其他视觉上独特的网页元素。为正文中使用的字体使用 font-display: optional

图标字体

适用于传统 Web 字体的 font-display 策略对图标字体来说效果不太理想。图标字体的后备字体通常与图标字体看起来完全不同,其字符可能传达完全不同的含义。因此,图标字体更有可能导致布局发生重大变化。

此外,使用回退字体可能并不实用。请尽可能将图标字体替换为 SVG,这也有助于提高可访问性。流行图标字体的较新版本通常支持 SVG。如需详细了解如何改用 SVG,请参阅 Font AwesomeMaterial 图标

减少后备字体和 Web 字体之间的偏移

如需减少 CLS 影响,您可以使用 size-adjust 属性

总结

网络字体仍然是性能瓶颈,但我们有越来越多的选项可供优化,以尽可能减少此瓶颈。