优化 WebFont 的加载和渲染

优化 WebFont 的加载和渲染

一个 “完整” 的 WebFont,其中包括你可能不需要的所有样式变体以及可能不使用的所有字形,都可以轻松导致数兆字节的下载。在本文中,你将了解如何优化 WebFonts 的加载,以便访问者仅下载他们将使用的内容。

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

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

默认行为

字体的延迟加载有一个重要的隐含含义,它可能会延迟文本渲染:浏览器必须构造渲染树(取决于 DOM 和 CSSOM 树),然后才能知道渲染该文本所需的字体资源。结果,字体请求在其他关键资源之后会延迟很长时间,并且浏览器可能无法呈现文本,直到获取资源为止。

字体加载渲染流程

  1. 浏览器请求 HTML 文档。

  2. 浏览器开始解析 HTML 响应并构建 DOM。

  3. 浏览器发现 CSS,JS 和其他资源并调度请求。

  4. 在收到所有 CSS 内容之后,浏览器将构造 CSSOM 并将其与 DOM 树组合以构造呈现树。

    • 在渲染树指示需要哪种字体变体以在页面上渲染指定的文本之后,分派字体请求。
  5. 浏览器执行布局并将内容绘制到屏幕上。

    • 如果字体尚不可用,则浏览器可能无法渲染任何文本像素。
    • 字体可用后,浏览器会绘制文本像素。

页面内容的第一次绘制(可以在构建渲染树后不久完成)和对字体资源的请求之间的“竞争”导致了“空白文本问题” ,即浏览器可能呈现页面布局但忽略任何文本。

通过预加载 WebFonts 并使用font-display来控制浏览器对不可用字体的行为,你可以防止由于加载字体而导致的空白页面和布局变化。

预加载 WebFont 资源

如果你的页面很有可能需要一个你事先知道的 URL 托管的特定 WebFont,你可以利用资源优先级。使用<link rel="preload">将在关键渲染路径的早期触发对 WebFont 的请求,而不必等待 CSSOM 被创建。

自定义文字延迟渲染

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

在文章中避免不可见的文本在字体加载期间,你可以看到默认的浏览器行为是不一致的。但是,你可以通过使用字体显示来告诉现代浏览器你希望它们的行为方式。

与某些浏览器实现的现有字体超时行为类似,将 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

字体加载 API

<link rel="preload">和 CSS 字体显示一起使用,可以在不增加太多开销的情况下对字体加载和渲染进行大量控制。但是,如果你需要额外的定制,并且愿意承受运行 JavaScript 带来的开销,那么还有另一种选择。

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())方法并跟踪它的下载进度,你还可以定义一个自定义策略来呈现你的页面上的文本:

  • 你可以保留所有文本渲染,直到字体可用为止。
  • 你可以为每种字体实现自定义超时。
  • 你可以使用后备字体取消阻止渲染,并在可用字体后注入使用所需字体的新样式。

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

字体加载 API 在较旧的浏览器中不可用。考虑使用 FontLoader polyfill 或 WebFontloader 库来提供类似的功能,尽管额外的 JavaScript 依赖会带来更多的开销。

正确的缓存是必须的

字体资源通常是不会频繁更新的静态资源。因此,它们非常适合较长的最大使用期限 - 确保为所有字体资源同时指定条件 ETag 标头和最佳 Cache-Control 策略。

如果你的 Web 应用程序使用 Service Worker,则使用缓存优先策略提供字体资源适用于大多数情况。

你不应该使用 localStorage 或 IndexedDB 存储字体;每个方面都有其自己的性能问题。浏览器的 HTTP 缓存提供了最佳,最可靠的机制,可将字体资源传递给浏览器。

web 字体加载列表

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