一个完整的webFont,可能包含你不需要的的所有变形字体,这很容易导致数兆字节的下载。这篇文章,我们将帮你了解如何优化webFont的加载,以此只访问你需要的内容。

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

基于此声明,这将使得浏览器非常方便的计算出所需的子集和变体,并下载渲染文本所需要的最小集合。但是,如果使用不当,它也会在关键渲染路径中造成性能瓶颈并延迟文本渲染。

默认表现

延迟字体加载将会有一个隐藏的影响效果,可能会造成文本渲染延迟:浏览器必须先构建依赖于 DOM 和 CSSOM 树的渲染树,然后才能知道它需要哪些字体资源来渲染文本。因此,字体请求会在其他关键资源之后延迟很长时间,并在获取资源之前浏览器可能都无法渲染文本。
image.png

1、浏览器请求html文档
2、浏览器开始解析 HTML 响应并构建 DOM。
3、浏览器发现 CSS、JS 和其他资源并发起请求。
4、浏览器在接收到所有 CSS 内容后构建 CSSOM,并将其与 DOM 树组合以构建渲染树。

  • 在渲染树指示需要哪些字体变体来渲染页面上的指定文本后分发字体请求。

5、浏览器执行布局并将内容绘制到屏幕上。

  • 如果字体尚不可用,浏览器可能不会呈现任何文本像素。
  • 字体可用后,浏览器会绘制文本像素。

    页面内容的第一次绘制(在渲染树构建完成后)与对字体资源的请求之间的“竞争”导致了“空白文本问题”(浏览器可能会渲染页面布局但忽略任何文本)
    通过预加载 WebFonts 以及使用 font-display 来控制浏览器如何处理不可用的字体,可以防止由于字体加载而导致的空白页面和布局偏移。

预加载WebFont 资源

你可以利用资源加载优先级提前在特定的URL上托管指定的webfont。使用将在关键渲染路径的早期触发对 WebFont 的请求,而无需等待创建 CSSOM。

自定义文本渲染延迟

虽然预加载在当页面内容渲染之后使webfont变的更可能可以使用,但是它并不保证一定可用。你仍然需要考虑浏览器在渲染文本时使用了尚不可用的字体时的表现。
在网页中的字体加载期间为了避免不可见文本的情况,你可以看到浏览器默认行为是不一致的。然而,你可以通过使用font-display告诉现代浏览器你想要他们如何展现。

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

1、第一个阶段是字体阻塞阶段。 在此期间,如果未加载字体,则任何尝试使用它的元素都必须渲染不可见的后备字体。 如果字体在此期间成功加载,则字体可以正常使用。
2、紧跟着字体阻塞阶段的是字体交换阶段。在此期间,如果字体未加载,任何尝试使用它的元素都必须渲染后备字体。如果在此期间字体已成功加载,则正常使用它。
3、紧跟着字体交换阶段的是字体失败阶段,如果未加载字体,浏览器将其视为加载失败,从而导致正常的字体回退。否则正常使用它。

了解这些时期意味着你可以使用 font-display 来决定你是否要加载或着何时下载字体来如何渲染。

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

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

font-display 目前支持以下值范围:

  • auto
  • block
  • swap
  • fallback
  • optional

字体加载 API

当你将和font-display一起使用时,你可以更好的控制字体的加载以及渲染,而不必担心太多的开销。但是如果你需要额外的定制,并且愿意承担运行 JavaScript 所带来的开销,还有另一种选择。

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

  1. var font = new FontFace("Awesome Font", "url(/fonts/awesome.woff2)", {
  2. style: 'normal', unicodeRange: 'U+000-5FF', weight: '400'
  3. });
  4. // don't wait for the render tree, initiate an immediate fetch!
  5. font.load().then(function() {
  6. // apply the font (which may re-render text and cause a page reflow)
  7. // after the font has finished downloading
  8. document.fonts.add(font);
  9. document.body.style.fontFamily = "Awesome Font, serif";
  10. // OR... by default the content is hidden,
  11. // and it's rendered after the font is available
  12. var content = document.getElementById("content");
  13. content.style.visibility = "visible";
  14. // OR... apply your own render strategy here...
  15. });

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

  • 你可以维持文本渲染状态,直到字体可用。
  • 你可以自定义实现每种字体超时。
  • 你可以使用回退字体来解锁渲染,并在字体可用后使用所需字体的新样式。

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

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

适当的缓存是必要的

字体资源通常是不经常更新的静态资源。 因此,它们非常适合设置长时间的 max-age 来控制是否过期(确保你为所有字体资源指定条件 ETag 标头和最佳缓存策略)。

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

因为浏览器的 HTTP 缓存提供了最好和最健壮的机制来向浏览器提供字体资源缓存策略。所以你不应该使用 localStorage 或 IndexedDB 存储字体;他们每一个都多多少少的有自己的一些性能问题。

WebFont 加载清单

  • 因为默认的延迟加载行为可能会导致文本渲染延迟,你可以使用 、font-display 或字体加载 API来自定义字体加载和渲染。 这些 Web 平台功能允许你为特定字体覆盖此行为,并为页面上的不同内容指定自定义渲染和超时策略。

  • 指定重新验证和最佳缓存策略:字体是不经常更新的静态资源。 确保您的服务器提供长期有效的 max-age 时间戳和重新验证令牌,以允许在不同页面之间有效地重用字体。 如果使用service worker,缓存优先策略是最合适的。

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

Lighthouse 可以帮助评估你是否遵循 Web 字体优化最佳实践的过程。
以下审核可以帮助你确保页面随着时间的推移继续遵循 Web 字体优化最佳实践: