优化 WebFont 的加载和呈现

Ilya Grigorik
Ilya Grigorik

一个“完整”网页字体包括您可能不需要的所有样式变体,加上可能不会使用的所有字形,很容易就会产生几兆字节的下载。在本文中,您将了解如何优化 Web 字体的加载,以便访问者仅下载他们将使用的字体。

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

鉴于这些声明,浏览器会确定需要的子集和变体,并下载渲染文本所需的最小集,非常方便。但如果您不小心,它也可能会在关键渲染路径中形成性能瓶颈并延迟文本渲染。

默认行为

字体延迟加载带有一个可能会延迟文本渲染的重要隐藏影响。 浏览器必须构建渲染树(它依赖 DOM 和 CSSOM 树),然后才能知道需要使用哪些字体资源来渲染文本。因此,字体请求的处理将远远滞后于其他关键资源请求的处理,并且在获取资源之前,可能会阻止浏览器渲染文本。

字体关键渲染路径

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

网页内容的首次绘制(可在渲染树构建后不久完成)与字体资源请求之间的“竞赛”产生了“空白文本问题”,出现该问题时,浏览器会在渲染网页布局时遗漏所有文本。

通过预加载 Web Fonts 并使用 font-display 控制浏览器在无法使用字体时的行为方式,您可以防止因字体加载而导致空白页面和布局偏移。

预加载 WebFont 资源

如果您的网页很有可能需要您事先在知道的网址上托管特定的 Web 字体,您可以利用资源优先级功能。使用 <link rel="preload"> 会在关键渲染路径中提早触发对网络字体的请求,而不必等待创建 CSSOM。

自定义文本渲染延迟

虽然预加载可以增加网络字体在页面内容渲染时可用的可能性,但并不保证一定如此。您仍需要考虑所渲染的文本使用了尚未可用的 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 属性,请参阅以下文章:

Font Loading API

<link rel="preload"> 和 CSS font-display 搭配使用可让您很好地控制字体加载和渲染,而不会增加太多开销。但是,如果您需要进一步自定义,而且愿意承担运行 JavaScript 所引入的开销,还有一个选项可供选择。

Font Loading 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 策略

如果您的网页应用使用 Service Worker,则使用缓存优先策略提供字体资源适合于大部分用例。

不应使用 localStorageIndexedDB 来存储字体;这两者自身都有一些性能问题。浏览器的 HTTP 缓存可以提供最佳且最可靠的机制来向浏览器提供字体资源。

WebFont 加载核对清单

  • 使用 <link rel="preload">font-display 或 Font Loading API 来自定义字体加载和渲染:默认的延迟加载行为可能会导致延迟渲染文本。您可以通过这些网络平台功能为特定字体替换这一行为,以及为网页上的不同内容指定自定义渲染和超时策略。
  • 指定重新验证和最佳缓存策略:字体是不经常更新的静态资源。确保您的服务器提供长期的 max-age 时间戳和重新验证令牌,以实现不同网页之间高效的字体重复使用。如果使用 Service Worker,则适合采用缓存优先策略。

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

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

以下审核可帮助您确保您的网页长期遵循 Web 字体优化最佳实践: