优化 WebFont 加载和渲染

一个“完整”的 WebFont 包含了您可能不需要的所有样式变体,以及可能未使用的所有字形,很容易导致几兆字节的下载量。在这篇文章中,您将了解如何优化 WebFont 的加载,以便访问者仅下载他们将使用的内容。

为了解决包含所有变体的大文件问题,@font-face CSS 规则专门设计用于将字体系列拆分为资源集合。例如,Unicode 子集和不同的样式变体。

考虑到这些声明,浏览器会找出所需的子集和变体,并下载渲染文本所需的最小集合,这非常方便。但是,如果您不小心,它也可能在关键渲染路径中造成性能瓶颈,并延迟文本渲染。

默认行为

字体的延迟加载带有一个重要的隐藏含义,可能会延迟文本渲染。浏览器必须构建渲染树,这取决于 DOM 和 CSSOM 树,然后它才知道渲染文本需要哪些字体资源。因此,字体请求会延迟到其他关键资源之后,并且浏览器可能会被阻止渲染文本,直到获取到资源。

Font critical rendering path

  1. 浏览器请求 HTML 文档。
  2. 浏览器开始解析 HTML 响应并构建 DOM。
  3. 浏览器发现 CSS、JS 和其他资源并调度请求。
  4. 浏览器在接收到所有 CSS 内容后构建 CSSOM,并将其与 DOM 树结合以构建渲染树。
    • 字体请求在渲染树指示渲染页面上指定的文本需要哪些字体变体后调度。
  5. 浏览器执行布局并将内容绘制到屏幕上。
    • 如果字体尚不可用,浏览器可能不会渲染任何文本像素。
    • 字体可用后,浏览器会绘制文本像素。

页面内容首次绘制(可以在渲染树构建后不久完成)与字体资源请求之间的“竞争”是造成“空白文本问题”的原因,在这种情况下,浏览器可能会渲染页面布局,但省略任何文本。

通过预加载 WebFont 并使用 font-display 来控制浏览器在字体不可用时的行为,您可以防止由于字体加载而导致的空白页面和布局偏移。

预加载您的 WebFont 资源

如果您的页面很可能需要托管在您预先知道的 URL 上的特定 WebFont,则可以利用资源优先级。使用 <link rel="preload"> 将在关键渲染路径中尽早触发对 WebFont 的请求,而无需等待 CSSOM 的创建。

自定义文本渲染延迟

虽然预加载使 WebFont 更有可能在页面内容渲染时可用,但它不提供任何保证。您仍然需要考虑浏览器在渲染使用尚未可用的 font-family 的文本时的行为。

在文章避免字体加载期间的不可见文本中,您可以看到默认浏览器行为不一致。但是,您可以使用 font-display 告诉现代浏览器您希望它们如何表现。

浏览器支持

  • Chrome: 60.
  • Edge: 79.
  • Firefox: 58.
  • Safari: 11.1.

来源

与某些浏览器实现的现有字体超时行为类似,font-display 将字体下载的生命周期分为三个主要时期

  1. 第一个时期是字体阻止期。在此期间,如果字体面未加载,则任何尝试使用它的元素都必须使用不可见的后备字体面进行渲染。如果字体面在阻止期内成功加载,则字体面将正常使用。
  2. 字体交换期紧随字体阻止期之后发生。在此期间,如果字体面未加载,则任何尝试使用它的元素都必须使用后备字体面进行渲染。如果字体面在交换期内成功加载,则字体面将正常使用。
  3. 字体失败期紧随字体交换期之后发生。如果字体面在此期间开始时仍未加载,则将其标记为加载失败,从而导致正常的字体后备。否则,字体面将正常使用。

了解这些时期意味着您可以使用 font-display 来决定您的字体应如何渲染,具体取决于它是否以及何时下载。

要使用 font-display 属性,请将其添加到您的 @font-face 规则中

@font-face {
  font-family: 'Awesome Font';
  font-style: normal;
  font-weight: 400;
  font-display: auto; /* or block, swap, fallback, optional */
  src: local('Awesome Font'),
       url('/fonts/awesome-l.woff2') format('woff2'), /* will be preloaded */
       url('/fonts/awesome-l.woff') format('woff'),
       url('/fonts/awesome-l.ttf') format('truetype'),
       url('/fonts/awesome-l.eot') format('embedded-opentype');
  unicode-range: U+000-5FF; /* Latin glyphs */
}

font-display 当前支持以下值范围

  • auto
  • block
  • swap
  • fallback
  • optional

有关预加载字体和 font-display 属性的更多信息,请参阅以下文章

字体加载 API

结合使用 <link rel="preload"> 和 CSS font-display,您可以在字体加载和渲染方面获得很大的控制权,而无需增加太多开销。但是,如果您需要额外的自定义,并且愿意承担运行 JavaScript 带来的开销,则还有另一种选择。

字体加载 API 提供了一个脚本接口,用于定义和操作 CSS 字体面、跟踪其下载进度以及覆盖其默认的延迟加载行为。例如,如果您确定需要特定的字体变体,则可以定义它并告诉浏览器立即启动字体资源的获取

浏览器支持

  • Chrome: 35.
  • Edge: 79.
  • Firefox: 41.
  • Safari: 10.

来源

var font = new FontFace("Awesome Font", "url(/fonts/awesome.woff2)", {
  style: 'normal', unicodeRange: 'U+000-5FF', weight: '400'
});

// don't wait for the render tree, initiate an immediate fetch!
font.load().then(function() {
  // apply the font (which may re-render text and cause a page reflow)
  // after the font has finished downloading
  document.fonts.add(font);
  document.body.style.fontFamily = "Awesome Font, serif";

  // OR... by default the content is hidden,
  // and it's rendered after the font is available
  var content = document.getElementById("content");
  content.style.visibility = "visible";

  // OR... apply your own render strategy here...
});

此外,由于您可以检查字体状态(通过 check() 方法)并跟踪其下载进度,因此您还可以定义自定义策略来渲染页面上的文本

  • 您可以暂停所有文本渲染,直到字体可用。
  • 您可以为每种字体实现自定义超时。
  • 您可以使用后备字体来解除阻止渲染,并在字体可用后注入使用所需字体的新样式。

最棒的是,您还可以为页面上的不同内容混合和匹配上述策略。例如,您可以延迟某些部分的文本渲染,直到字体可用,使用后备字体,然后在字体下载完成后重新渲染。

正确的缓存是必须的

字体资源通常是静态资源,不会频繁更新。因此,它们非常适合长期 max-age 过期时间——确保您为所有字体资源指定条件 ETag 标头最佳 Cache-Control 策略

如果您的 Web 应用程序使用 Service Worker,则使用 缓存优先策略提供字体资源对于大多数用例都是合适的。

您不应使用 localStorageIndexedDB 存储字体;这些方法中的每一种都有其自身的性能问题。浏览器的 HTTP 缓存提供了向浏览器交付字体资源的最佳和最可靠的机制。

WebFont 加载清单

  • 使用 <link rel="preload">font-display 或字体加载 API 自定义字体加载和渲染:默认的延迟加载行为可能会导致文本渲染延迟。这些 Web 平台功能允许您覆盖特定字体的此行为,并为页面上的不同内容指定自定义渲染和超时策略。
  • 指定重新验证和最佳缓存策略:字体是静态资源,很少更新。确保您的服务器提供长期 max-age 时间戳和重新验证令牌,以便在不同页面之间有效重用字体。如果使用 Service Worker,则缓存优先策略是合适的。

使用 Lighthouse 自动测试 WebFont 加载行为

Lighthouse 可以帮助自动化确保您遵循 Web 字体优化最佳实践的过程。

以下审核可以帮助您确保您的页面随着时间的推移继续遵循 Web 字体优化最佳实践