优化 WebFont 的加载和呈现

Ilya Grigorik
Ilya Grigorik

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

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

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

默认行为

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

字体关键渲染路径

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

网页内容的首次绘制(可在渲染树构建后不久完成)与字体资源请求之间的“竞态”导致了“空白文本问题”,在这种情况下,浏览器可能会渲染页面布局,但会省略所有文本。

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

预加载 WebFont 资源

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

自定义文本呈现延迟

虽然预加载可以提高 WebFont 在网页内容呈现时可用的可能性,但并不保证一定如此。 您仍然需要考虑浏览器在渲染使用 font-family(尚不可用)的文本时的行为。

避免在字体加载过程中使用不可见文本一文中,您可以看到默认的浏览器行为不一致。不过,您可以使用 font-display 告知现代浏览器您希望它们的行为方式。

浏览器支持

  • 60
  • 79
  • 58
  • 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 所引入的开销,还有另一种选择。

Font Loading API 提供了一种脚本编程接口来定义和操纵 CSS 字体,跟踪其下载进度,以及替换其默认延迟加载行为。例如,如果您确定需要特定的字体变体,可以定义该变体并指示浏览器立即提取字体资源:

浏览器支持

  • 35
  • 79
  • 41
  • 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 或 Font Loading API 自定义字体加载和渲染:默认的延迟加载行为可能会导致文本渲染延迟。借助这些 Web 平台功能,您可以替换特定字体的这种行为,并为网页上的不同内容指定自定义渲染和超时策略。
  • 指定重新验证和最佳缓存政策:字体是不经常更新的静态资源。确保您的服务器提供长期的 max-age 时间戳和重新验证令牌,以实现不同页面之间高效的字体重复使用。如果使用 Service Worker,则适合采用缓存优先策略。

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

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

以下审核可帮助您确保网页始终遵循网页字体优化最佳实践: