最佳字体加载策略以及如何执行

Avatar of Zell Liew
Zell Liew 发布

DigitalOcean 为您旅程的每个阶段提供云产品。立即开始使用 200 美元的免费额度!

Zach Leatherman 撰写了一篇 关于字体加载策略的综合列表,该列表在 Web 开发领域被广泛分享。我之前看过这份列表,但感到非常害怕(和困惑),以至于我决定什么也不做。我不知道如何以最佳方式开始加载字体,也不想在浏览这份列表时感觉自己像个傻瓜。

今天,我给自己留出时间 坐下来弄清楚。在这篇文章中,我想与您分享最佳加载策略以及如何执行所有这些策略。

最佳策略

Zach 在他的文章中推荐了两种策略

  1. 带有类的 FOUT – 大多数情况下的最佳方法。(无论我们使用字体托管公司还是自己托管字体,这都适用。)
  2. 关键 FOFT – 性能最佳的方法。(这仅在我们自己托管字体时有效。)

在深入探讨这两种策略之前,我们需要先澄清这些缩写词,以便您了解 Zach 的意思。

FOIT、FOUT、FOFT

缩写词如下

  • FOIT 表示不可见文本闪烁。当 Web 字体加载时,我们会隐藏文本,以便用户看不到任何内容。我们仅在 Web 字体加载后显示文本。
  • FOUT 表示未设置样式的文本闪烁。当 Web 字体加载时,我们会向用户显示系统字体。当 Web 字体加载完成后,我们会将文本更改回所需的 Web 字体。
  • FOFT 表示伪文本闪烁。这个比较复杂,需要更多解释。当我们进入 FOFT 部分时,我们将详细讨论它。

自托管字体与云托管字体

有两种主要方法可以托管字体

  1. 使用云提供商。
  2. 自托管字体。

我们加载字体的方式在很大程度上取决于您选择的选项。

使用云托管字体加载字体

使用云托管字体通常更容易。提供商将提供一个加载字体的链接。我们可以简单地将此链接放入我们的 HTML 中,然后就可以获取我们的 Web 字体。以下是一个示例,我们现在从 Adobe Fonts(以前称为 Typekit)加载 Web 字体。

<link rel="stylesheet" href="https://use.typekit.net/your-kit-id.css">

不幸的是,这不是最佳方法。href 会阻塞浏览器。如果加载 Web 字体挂起,用户将无法执行任何操作,只能等待。

Typekit 曾经提供过异步加载字体的 JavaScript。很遗憾,他们不再显示此 JavaScript 版本。(代码仍然有效,但我不知道何时或是否会停止工作。)

从 Google Fonts 加载字体稍微好一点,因为它提供了font-display: swap。以下是一个从 Google Fonts 加载 Lato 的示例。(display=swap 参数会触发font-display: swap)。

<link
  href="https://fonts.googleapis.com/css2?family=Lato:ital,wght@0,400;0,700;1,400;1,700&display=swap"
  rel="stylesheet"
/>

使用自托管字体加载字体

只有在您拥有许可证的情况下才能自托管您的字体。由于字体可能很昂贵,因此大多数人选择使用云提供商。

不过,有一种获取字体的廉价方法。Design Cuts 偶尔会进行字体促销活动,您可以以每个捆绑包仅 29 美元的价格获得质量极高的字体。每个捆绑包最多可以包含 12 种字体。我设法获得了 Claredon、DIN、Futura 等经典字体,以及一大堆可以玩的字体,方法是在他们的时事通讯上蹲点等待这些促销活动。

如果我们想自托管字体,我们需要了解另外两个概念:@font-facefont-display: swap

@font-face

@font-face 允许我们在 CSS 中声明字体。如果我们想自托管字体,我们需要使用@font-face 来声明我们的字体。

在此声明中,我们可以指定四件事

  • font-family:这告诉 CSS(和 JavaScript)我们字体的名称。
  • src:查找字体的路径,以便它们可以加载
  • font-weight:字体粗细。如果省略,则默认为 400。
  • font-style:是否要使字体斜体。如果省略,则默认为 normal。

对于src,我们需要指定几种字体格式。为了 获得实用的浏览器支持水平,我们可以使用woff2woff

以下是一个通过@font-face 加载 Lato 的示例。

@font-face {
  font-family: Lato;
  src: url('path-to-lato.woff2') format('woff2'),
       url('path-to-lato.woff') format('woff');
}

@font-face {
  font-family: Lato;
  src: url('path-to-lato-bold.woff2') format('woff2'),
       url('path-to-lato-bold.woff') format('woff');
  font-weight: 700;
}

@font-face {
  font-family: Lato;
  src: url('path-to-lato-italic.woff2') format('woff2'),
       url('path-to-lato-italic.woff') format('woff');
  font-style: italic;
}

@font-face {
  font-family: Lato;
  src: url('path-to-lato-bold-italic.woff2') format('woff2'),
       url('path-to-lato-bold-italic.woff') format('woff');
  font-weight: 700;
  font-style: italic;
}

如果您没有woff2woff 文件,您可以将字体文件(Opentype 或 Truetype)上传到 字体生成器 以获取它们。

接下来,我们在font-family 属性中声明 Web 字体。

html {
  font-family: Lato, sans-serif;
}

当浏览器解析带有 Web 字体的元素时,它们会触发 Web 字体的下载。

font-display: swap

font-display 可以取四个可能的值之一:autoswapfallbackoptionalswap 指示浏览器在 Web 字体加载之前显示回退文本。换句话说,swap 会为自托管字体触发 FOUT。您可以在 CSS-Tricks 年鉴 中了解其他值。

font-display: swap 的浏览器支持现在相当不错,因此我们可以在项目中使用它。

此浏览器支持数据来自 Caniuse,其中包含更多详细信息。数字表示浏览器从该版本开始支持该功能。

桌面

ChromeFirefoxIEEdgeSafari
60587911.1

移动/平板电脑

Android ChromeAndroid FirefoxAndroidiOS Safari
12712712711.3-11.4

FOUT 与带有类的 FOUT

FOUT 表示未设置样式的文本闪烁。您始终希望 FOUT 优于 FOIT(不可见文本闪烁),因为与使用隐形墨水编写的文字相比,用户更容易阅读用系统字体编写的文字。我们之前提到过,font-display: swap 使您能够本地使用 FOUT。

带有类的 FOUT 会为您提供相同的结果——FOUT——但它使用 JavaScript 加载字体。其机制如下

  • 首先:加载系统字体。
  • 其次:通过 JavaScript 加载 Web 字体。
  • 第三:加载 Web 字体后,向<html> 标签添加一个类。
  • 第四:用加载的 Web 字体替换系统字体。

尽管font-display: swap 拥有良好的浏览器支持,但 Zach 建议通过 JavaScript 加载字体。换句话说,Zach 建议使用带有类的 FOUT 而不是@font-face + font-display: swap

他推荐使用带有类的 FOUT 是基于以下三个原因

  1. 我们可以对重绘进行分组。
  2. 我们可以适应用户偏好。
  3. 如果用户连接缓慢,我们可以完全跳过字体加载。

我将让您在 另一篇文章 中更深入地了解这些原因。在撰写本文时,我发现了一个偏好带有类的 FOUT 的第四个原因:当用户已经在其系统中加载了字体时,我们可以跳过字体加载。(我们使用sessionStorage 来做到这一点,我们将在下面看到。)

使用类进行 FOUT(适用于云托管字体)

首先,我们像往常一样从您的云托管公司加载字体。以下是一个我从 Google Fonts 加载 Lato 的示例。

<head>
  <link
    href="https://fonts.googleapis.com/css2?family=Lato:ital,wght@0,400;0,700;1,400;1,700&display=swap"
    rel="stylesheet"
  />
</head>

接下来,我们想通过 JavaScript 加载字体。由于代码占用空间小,并且无论如何都是异步的,因此我们将把一个 script 注入到 <head> 部分。

<head>
  <link
    href="https://fonts.googleapis.com/css2?family=Lato:ital,wght@0,400;0,700;1,400;1,700&display=swap"
    rel="stylesheet"
  />
  <script src="js/load-fonts.js"></script>
</head>

load-fonts.js 中,我们想使用 CSS 字体加载 API 来加载字体。在这里,我们可以使用 Promise.all 来同时加载所有字体。这样做时,我们正在对重绘进行分组。

代码如下所示

if ('fonts' in document) {
  Promise.all([
    document.fonts.load('1em Lato'),
    document.fonts.load('700 1em Lato'),
    document.fonts.load('italic 1em Lato'),
    document.fonts.load('italic 700 1em Lato')
  ]).then(_ => () {
    document.documentElement.classList.add('fonts-loaded')
  })
}

加载字体后,我们希望将正文文本更改为网页字体。我们可以通过使用 .fonts-loaded 类在 CSS 中执行此操作。

/* System font before [[webfont]]s get loaded */
body {
  font-family: sans-serif;
}

/* Use [[webfont]] when [[webfont]] gets loaded*/
.fonts-loaded body {
  font-family: Lato,  sans-serif;
}

请注意:使用此方法需要使用 .fonts-loaded 类。

我们不能直接在 <body>font-family 中编写网页字体;这样做会触发字体下载,这意味着您正在使用 @font-face + font-display: swap。这也意味着 JavaScript 是多余的。

/* DO NOT DO THIS */
body {
  font-family: Lato, sans-serif;
}

如果用户访问网站上的其他页面,他们的浏览器中已经加载了字体。我们可以跳过字体加载过程(以加快速度),方法是使用 sessionStorage

if (sessionStorage.fontsLoaded) {
  document.documentElement.classList.add('fonts-loaded')
} else {
  if ('fonts' in document) {
  Promise.all([
    document.fonts.load('1em Lato'),
    document.fonts.load('700 1em Lato'),
    document.fonts.load('italic 1em Lato'),
    document.fonts.load('italic 700 1em Lato')
  ]).then(_ => () {
    document.documentElement.classList.add('fonts-loaded')
  })
  }
}

如果我们想优化 JavaScript 的可读性,可以使用早期返回模式来减少缩进。

function loadFonts () {
  if (sessionStorage.fontsLoaded) {
    document.documentElement.classList.add('fonts-loaded')
    return 
  } 
  
if ('fonts' in document) {
  Promise.all([
    document.fonts.load('1em Lato'),
    document.fonts.load('700 1em Lato'),
    document.fonts.load('italic 1em Lato'),
    document.fonts.load('italic 700 1em Lato')
  ]).then(_ => () {
    document.documentElement.classList.add('fonts-loaded')
  })  
}

loadFonts()

使用类进行 FOUT(适用于自托管字体)

首先,我们想通过 @font-face 像往常一样加载字体。由于我们通过 JavaScript 加载字体,因此 font-display: swap 属性是可选的。

@font-face {
  font-family: Lato;
  src: url('path-to-lato.woff2') format('woff2'),
       url('path-to-lato.woff') format('woff');
}

/* Other @font-face declarations */

接下来,我们通过 JavaScript 加载网页字体。

if ('fonts' in document) {
  Promise.all([
    document.fonts.load('1em Lato'),
    document.fonts.load('700 1em Lato'),
    document.fonts.load('italic 1em Lato'),
    document.fonts.load('italic 700 1em Lato')
  ]).then(_ => () {
    document.documentElement.classList.add('fonts-loaded')
  })
}

然后,我们想通过 CSS 将正文文本更改为网页字体。

/* System font before webfont is loaded */
body {
  font-family: sans-serif;
}

/* Use webfont when it loads */
.fonts-loaded body {
  font-family: Lato,  sans-serif;
}

最后,我们跳过重复访问的字体加载。

if ('fonts' in document) {
  Promise.all([
    document.fonts.load('1em Lato'),
    document.fonts.load('700 1em Lato'),
    document.fonts.load('italic 1em Lato'),
    document.fonts.load('italic 700 1em Lato')
  ]).then(_ => () {
    document.documentElement.classList.add('fonts-loaded')
  })
}

CSS 字体加载器 API 与 FontFaceObserver

CSS 字体加载器 API 拥有相当不错的浏览器支持,但它是一个相当难用的 API。

此浏览器支持数据来自 Caniuse,其中包含更多详细信息。数字表示浏览器从该版本及更高版本开始支持该功能。

桌面

ChromeFirefoxIEEdgeSafari
35417910

移动/平板电脑

Android ChromeAndroid FirefoxAndroidiOS Safari
12712712710.0-10.2

因此,如果您需要支持旧版浏览器(如 IE 11 及以下版本),或者您发现此 API 奇怪且难以使用,则可能需要使用 Bramstein 的 FontFaceObserver。它非常轻量级,因此没有太大害处。

代码如下所示。(与 CSS 字体加载器 API 相比,它好得多)。

new FontFaceObserver('lato')
  .load()
  .then(_ => {
    document.documentElement.classList.add('fonts-loaded')
  })

如果您打算让它在不支持 Promise 的浏览器上加载字体,请确保使用 fontfaceobserver.standalone.js

FOFT

FOFT 表示伪文本闪现。这里的想法是我们将字体加载分为三个阶段

  • 步骤 1:当网页字体尚未加载时,使用回退字体。
  • 步骤 2:首先加载字体的罗马体(也称为“书籍”或“常规”)版本。这将替换大部分文本。粗体和斜体将由浏览器伪造(因此称为“伪文本”)。
  • 步骤 3:加载其余字体

注意:Zach 也称之为标准 FOFT

使用标准 FOFT

首先,我们加载罗马体字体。

@font-face {
  font-family: LatoInitial;
  src: url('path-to-lato.woff2') format('woff2'),
       url('path-to-lato.woff') format('woff');
  unicode-range: U+65-90, U+97-122;
}

.fonts-loaded-1 body {
  font-family: LatoInitial;
}
if('fonts' in document) {
  document.fonts.load("1em LatoInitial")
    .then(_ => {
      document.documentElement.classList.add('fonts-loaded-1')
    })
}

然后,我们加载其他字体。

请注意:我们再次加载 Lato,但这次我们将 font-family 设置为 Lato 而不是 LatoInitial

/* Load this first */
@font-face {
  font-family: LatoInitial;
  src: url('path-to-lato.woff2') format('woff2'),
       url('path-to-lato.woff') format('woff');
  unicode-range: U+65-90, U+97-122;
}

/* Load these afterwards */
@font-face {
  font-family: Lato;
  src: url('path-to-lato.woff2') format('woff2'),
       url('path-to-lato.woff') format('woff');
  unicode-range: U+65-90, U+97-122;
}

/* Other @font-face for different weights and styles*/

.fonts-loaded-1 body {
  font-family: LatoInitial;
}

.fonts-loaded-2 body {
  font-family: Lato;
}
if ('fonts' in document) {
  document.fonts.load('1em LatoInitial')
    .then(_ => {
      document.documentElement.classList.add('fonts-loaded-1')

      Promise.all([
        document.fonts.load('400 1em Lato'),
        document.fonts.load('700 1em Lato'),
        document.fonts.load('italic 1em Lato'),
        document.fonts.load('italic 700 1em Lato')
      ]).then(_ => {
        document.documentElement.classList.add('fonts-loaded-2')
      })
    })
}

同样,我们可以针对重复查看对其进行优化。

在这里,我们可以直接在 <html> 中添加 fonts-loaded-2,因为字体已加载。

function loadFonts () {
  if (sessionStorage.fontsLoaded) {
    document.documentElement.classList.add('fonts-loaded-2')
    return
  }

  if ('fonts' in document) {
    document.fonts.load('1em Lato')
      .then(_ => {
        document.documentElement.classList.add('fonts-loaded-1')

        Promise.all([
          document.fonts.load('400 1em Lato'),
          document.fonts.load('700 1em Lato'),
          document.fonts.load('italic 1em Lato'),
          document.fonts.load('italic 700 1em Lato')
        ]).then(_ => {
          document.documentElement.classList.add('fonts-loaded-2')

          // Optimization for Repeat Views
          sessionStorage.fontsLoaded = true
        })
      })
  }
}

关键 FOFT

“关键”部分来自“关键 CSS”(我相信)——我们只在加载其余内容之前加载必要的 CSS。我们这样做是因为它提高了性能。
在排版方面,唯一关键的是字母 A 到 Z(大写和小写字母)。我们可以使用 unicode-range 创建这些字体的子集。

创建此子集时,我们还可以使用必要的字符创建单独的字体文件。

以下是 @font-face 声明的样子

@font-face {
  font-family: LatoSubset;
  src: url('path-to-optimized-lato.woff2') format('woff2'),
       url('path-to-optimized-lato.woff') format('woff');
  unicode-range: U+65-90, U+97-122;
}

我们首先加载此子集。

.fonts-loaded-1 body {
  font-family: LatoSubset;
}
if('fonts' in document) {
  document.fonts.load('1em LatoSubset')
    .then(_ => {
      document.documentElement.classList.add('fonts-loaded-1')
    })
}

然后我们稍后加载其他字体文件。

.fonts-loaded-2 body {
  font-family: Lato;
}
if ('fonts' in document) {
  document.fonts.load('1em LatoSubset')
    .then(_ => {
      document.documentElement.classList.add('fonts-loaded-1')

      Promise.all([
        document.fonts.load('400 1em Lato'),
        document.fonts.load('700 1em Lato'),
        document.fonts.load('italic 1em Lato'),
        document.fonts.load('italic 700 1em Lato')
      ]).then(_ => {
        document.documentElement.classList.add('fonts-loaded-2')
      })
    })
}

同样,我们可以针对重复查看对其进行优化

function loadFonts () {
  if (sessionStorage.fontsLoaded) {
    document.documentElement.classList.add('fonts-loaded-2')
    return
  }

  if ('fonts' in document) {
    document.fonts.load('1em LatoSubset')
      .then(_ => {
        document.documentElement.classList.add('fonts-loaded-1')

        Promise.all([
          document.fonts.load('400 1em Lato'),
          document.fonts.load('700 1em Lato'),
          document.fonts.load('italic 1em Lato'),
          document.fonts.load('italic 700 1em Lato')
        ]).then(_ => {
          document.documentElement.classList.add('fonts-loaded-2')

          // Optimization for Repeat Views
          sessionStorage.fontsLoaded = true
        })
      })
  }
}

关键 FOFT 变体

Zach 提出了另外两种关键 FOFT 变体

  • 使用 Data URI 的关键 FOFT
  • 使用预加载的关键 FOFT

使用数据 URI 的关键 FOFT

在此变体中,我们使用数据 URI 而不是普通链接来加载关键字体(子集化的罗马体字体)。以下是它的样子。(而且看起来很吓人)。

@font-face {
  font-family: LatoSubset;
  src: url("data:application/x-font-woff;charset=utf-8;base64,d09GRgABAAAAABVwAA0AAAAAG+QAARqgAAAAAAAAAAAAAAAAAAAAAAAAAABHREVGAAABMAAAABYAAAAWABEANkdQT1MAAAFIAAACkQAAA9wvekOPT1MvMgAAA9wAAABcAAAAYNjUqmVjbWFwAAAEOAAAADgAAABEAIcBBGdhc3AAAARwAAAACAAAAAgAAAAQZ2x5ZgAABHgAAA8EAAAUUN1x8mZoZWFkAAATfAAAADYAAAA2DA2UbWhoZWEAABO0AAAAHgAAACQPOga/aG10eAAAE9QAAADIAAAA2PXwFPVsb2NhAAAUnAAAAG4AAABuhQSA721heHAAABUMAAAAGgAAACAAOgBgbmFtZQAAFSgAAAA0AAAANAI2Codwb3N0AAAVXAAAABMAAAAg/3QAegABAAAADAAAAAAAAAACAAEAAQA1AAEAAHicTZO/SxthGMe/d4k2NFbSFE2Maaq2tJ4/qtE4dwnBoUgoHUq5pWBBaCv0h4OCS2MLGUuXIhlKhwxFMnVwcAvB4SiSQSRkOEK9xan/wdvPRYQSnrzv3fu8n8/7vO97siRd1z0tyS6WHj/V8OsXHzY1rCjvZYzCcevVy3ebioW9fkRl99sYUepn5vTZWtOhdW7v6MJas+aIDeujdW5d2GV7x/4VSUaKkSf8ipFN4rK/EdnjaQ9KDuKArimuId1QQjeV1C2NaFQppTWmjMaV1W1N6K7ua1qOZjSreeW1rAJzouZUMSLOc4I2SYyYbY2aY6XMltLmpzLmmfLmQAViSDajcbOinDnUHWKCmDZNOcQM/VnaOdp52kUigaOBowG/Ab8Bvwy7BHsd9gNIXUhdSF0IXWZ38X3RErkF2hjO9zhLyprfZPfI7pHdI7tHdk+DZITrrsH1NMabDHPHWcUg9jb2NvY29jZZHtWdsjthJVHmxIi4CeuvkVHDwrkYB4uDxdGUeUSFLhW6GB0qdLE6VOjqoXlOlS7rXWW9b7Vs3rDmVa2YT5yOzS6O8b+Fx8Pj4VnH4+Hx8FTxVPFU8VQ1ybqnyJ02dVx1XFVcdVxVXHVcX7XAyhfp580JLg/XCa4DbqJtvkP/BrUO1YfqQ/Uh1iD5UHwI7fCG4okRCSJJ/L//kzCvzmABbt4cUdcxniNulM1NuDrNyx27PNGs2YXiQnGhuDjLVFGhigo0lyoqEF2qqLCGXSqoQN6H/IMqtqHv9+9iC3ILagtqC2IHYgdiB0oHQofZf5h5xowzRej5MP7y5PMVpNinNL28CaA2eRtwBllmDcBqwmrCCm9peEOb/VtzwEjASMBIwEjASPAPOZBmnAAAAHicY2BmEWWcwMDKwMI6i9WYgYFRHkIzX2SoZmLgYGbiZ2ViYmJhZmJewMCwPoAhwZsBCkoqA3wYHBh4fzOxFf4rZGBgj2Scp8DAMBkkx8LLuhtIKTAwAQBlVA2xeJxjYGBgYmBgYAZiESDJCKZZGAyANAcQguQUGKIYqv7/B7McGRL/////8P/B/7vBasEAAOVfC4UAAQAB//8AD3ichVgJVFRnln7/e/VqoahX26uFWqmNYpEqoCiKHYRiK4tNggIiICAIgigqRhHbBQVcA+0yUUIE3JOo7aQx3WQhiUI66U4653TrxE5m5vSkz0x3O2cS0/GcBt5z/ldVEHTszDkUVf+r/9z73+9+97v3LwRFghAEb8EnEQEiQxAHcACTMcyKGTACmIABC7MSgBOErvgY9bysNnAHqE2HOaYQwELN/6mQ8WV8fHI2R02CenpEokUNaLmrLDJPhWDILQRhvQ2tkogeiYR2xYY4HSojCZSjA8ybyWKIS0ed8WEmg3Px0y3wu747e1KN7g05o+Pu3qluegqkle8qtoyO03cA+4XuUuvwBfo7fNJZf7wqoXGVWxZ6ZWDt2Y7UEWtufXJn7zHL8hrXvp0IApDqJ9/g4fivERuCSOMlCWZHnFzBgSEZbSyTkYAn0aEO6NaV4IjTAzZ8Zg5Dq9s+AKLxsb+uWc6RSAS6CJe3Mavrw2PFxf2/bMvaUFloFYtERFEV/eTnr9L0xHr0q0tANr1p/ao1QQKp1qAhS0/fPzhw/1Sh0BBnEgrKN2xvnwYkgjJY4NcgFsFIiB8JllwiI1HoNQCCDYWxo5YxEPTehg3v0Y/H6HGwtmuqz+Ppm+qix/HJlg/oRxcv0o/ebxktGvp9X9/vTxbBGBmMk6Fdvs+qzBB43cL6qUw0mZpGp/DJUVp5juaPLdnNW7rbv7eKugR3Uk/GqIEF7OogdvGL2OlQBYdtNVpt2FPgiSFV0oBBTGB+/Fo/BPLXV5/a225PFGtEpCpjze7y3b865i0+cXdHSkNFkfU/pErwgevF/uE15+nHU23oV1eA7G67KiYnqkoD+EREmLr0zL2DRx78U0mwPJQEboVoC/Xr8JRwqQ9JXwx4xULEMAr4MsH/tz7C5B99NP8XfJLqRI/P5qDDVCPcfR8GMwJ3Y/7d92cYssLnaU++QR/A5woEcaUD+AfJwcTjhJsYahrTUA6GyuSSdIe7pcAyndr1+uaGYF2yViyXhK/qq8buzGd2f3goj8FqBp6oCtoy+n044hJcIB2kAADLRgcUDOFQK7BhUUA8c3eF1SYD/6WIMFylPpdb5NooBZr/ulQtFXNpnE+GGEOgJaqHVKARKhk1oDQE8SwaqoAdJOahecFiAU7V+THA/gY94gEMZLdm0G4Y2cPLiP9b9tXF80DSA3gKGKTvUBCseHg+dhQwWuFzmfjWT9UaAZgj5EFBCgJ8K9Cqh66jKM1TRWttKorGMHwySDH/YohTo41XYX1K3mwOK0Eeq5pvtdmwl9UO+dzHS7IiW/RpAwyWjJD43TSrQ0TYSgy93qgMFc7fQFF8UkDOXVfHKlnC2RwRySpVxZJzf4G1UvPkWzYOuSdFrIvsWywXhmSuAPl8VQPJWNPyLggaGwN8X+l8PzZGP3635RNP/1TXjqn+goL+qR1dU/0e9KuLgJzp6JihH8I6+u+Z9vYZIL3Yd+9kcfHJe31990+VlJyCdEGZfLLOwViIhVr1q1YUCAF+r2FRYAb87Minfdn5h3/T+/Bh+f6K6DduPMQnM3dcaW56rdtN/QH9MLp0s/voCGOvhv6c7YHxaJCYp+OBSuhTWsCI0o/HFVnSmQ92XrAkKWk6eU/O/x9h8+T5A7F0K6kCIRLZj0SKw/wiSsT0lD7LGdJCyoJnYh458klvZuzavhcsGvD6zQQVfUAcEXHmt6t7K6PfeO0LfDKx9czaor62FaQ8nPpFJBocIqOG0e8jC1uXHzjIMDPlyTfYdxCJNATJAEzNLcbLdsanoz8INBrQGLavTxjDUpK5CqHcVba9PK/DG5Fav6d3T0Nq6tarm7o/9sbwZCKxPbcxN7spx5TWwHyVlrXn9vbjf71RwxfZXHZrXn1a9gvJ4ZGuit51RUOb3UWeGqHIFGkyp5XFpK1MioxOXNVTXXe1J68ZnlELs38WYsKBuXIy+mgSa1k9tGUaJy9fnn2IMxp04cn/4PFwD+SHlBGMQBgL2mFDL9x1NgyuWXOiwXm39PQXfX0PzpSiGdju+UPVZzvS0zperoafe3/yxUhV1cgDaI8L89AM7UmgvWcLFpi4r6pDueBVQsnnyQnwCsekepW69s/4pEo2971p5bJlK00svlgHZQ1aIhCEq4SWwp9jiVnKl9gFvsYOTMRFtYkN7FwBh0PwQCXbrEpSGzlgtUTIDuKCGNysOkcn3KKP8oNwHk4fvsV4nmdpMkym5SpsXqxjVk63MQ0uJFqoDrWhkWSYaG40cB6chOdRQ37xgO9EAXXk+anP5jDsJ8Dfac1dOckDP4EEaIUZfYdWgcd3FCFcuovDp/fxlDL0EforQkC9KQ9BQwgxlUnhSgJtlkupDwhlAEPUN208B0OYRO51tZ4PSkgNn6+WgSJeqOom9ae7MBaSmjZn6EMzTGiySDd/jHoLzWdOXgF5cNM3EyEOZ7wLwBlFRnKAQVZBYhnzv2PFyubfx3KL9GrW0GiBST/XOeardcjwE6xQJAJJYmodNkhmqjH6SGwPUF4m9i2YjmlbrHmXk8Bq3Kf/drO5KbsqO0Yp0XANq2Z2ru6rtpeqQ3FZROGq+pTU1iLbDXV0mjmmJC9Tv+dmRxzA0jorklilO7uNEUapMLlsZXLT4Gpqg0hdZ0kOJw3u5uKIlDCx1Oww/Asr1JHLnHH4ySOWHf8akSMWf/8IHMtfalYO2+TSwScJfhlic4ZvL7/UVjXY7ErbdnlD47F4Hjc8K/ZFz+Cw2d2QXnkoEf+aOrlijfvQ9L6td18qK8ytsc66XZ+9t35obdRKD0TS++QRdpKlY2ZA6XOr3AcAM4T5n3ozd090tV/JjOGKREKzqzjZu604PKpoc072qiSLRMl3uD/evO5Kdy7K2XbnBAQiiy9Q6UMSmoaqaoYa40OtenF2mTdvYJqJ1QN9f7kQ64J3X1+yB7z5E5DgEtvgku1J23appbbfOdFm5Yuyr3VUDjW53ja56zMq+xLjXiwYHEaxrXeHypYnoxmzmrA9pevcB2f2rR+Eka4Aj93OTxmfTLz/CuM1I9ELPhl02SaDDluM1J98gx9ib1bPm1vWjmzNlKioIjR21Q5PTkNejEQR7DDWtHUmtb3Z6/klGm9016f3nkE17W8fLs7oemNTlLZ+aF1MqAXGG5FqleYf++yPGa2FkS8z7IX1hl7AP0d0jIo5/OLKoA7bPoFqgV/XrDagHnesPbAyN4UFDDq9vTBRC5bRD97qlKux6ysaK4/UxsrWkNwQV1V2be/8EaxTgMvh6IE46MPYY5YecrwQqUYQBZwoYLtn2O5/Y0CG1SIEcj+poM78IOa2pX2OBb+SLi7DjGzHuvJl8bAThRauTtkwuMq9PQaoorpMKc3HywuWGwwpDT37e+pTsnp+vqXztc1J582eDq9nW2lUVMG61o645IJ0XVJ5gqs8Sbvty20NhRtDyZwkRYw9ShR1ota7e7Vdp8028sTZpd7dFXYxaVdYLGIWT+Goys/evS4l2ttQZEpdplLHuiOs9hABzuZqitF/txW69HpXoa12yxYG2dMQgO8ho2RP1Q4zVoUxxSI+PZE92lq+f3X07Y0dpUdTYHGM5JUnNQ9WUm3oya79RVkUC3KEaYcD+D044yiWcsSPD9NTDspMdpXabpbJzHa12maSWSbwMnWMmSTNMXDNvNtmP2IJ5r5D0Ccz9BGfNZLRfWxhmk1YbEhhz3pI4qKEiCshSrInDLmbi4yu8Wfd0efrBVzgKWNVzV1Kbyux8dnvPOMdYjEMNVIEsdD9w8k3zD9FiIdv20m1GOQJ9apL9H5CI5HoBeDUuCKUoF8T6vUO/Ov5Ab4Y1IpkdKtYHRRsIGlELAVX5QJaxKB+Df6rg54Cc/y1CfzrWY0/G2ypX1sY/3qfxMImhy3NCvFUftqsQUTS8ZqWA1p5bkWDY+XeSvtEa1N0aZp5Yn2de2sMSxDWmle1vSFxpTPE2ThUy+Rt1x59+tp05lNPd37G/NwCD6BnxVLPumc9Cd2XNv1ABmh684rqZ8jgVymWFdqSMnPYs7kKCFSgj3hy9t3u7Ly9NydnL/O+L+ftsMLOorNnzpwt6iwMQzm7po94vUemd3XfOerxHL2zq3qw0fnpW7/4zNn4U8bTaXqUFQ77k18Pl547oP4h4CntZ2KAOri5Esp/eufllrr+eCiDL70SkH56FG8L21Van3Noxif+GSm0E61cov4wNnoU+3LRY0AcXAm+m8g/VODGY67goPC2iazLG5cqcOzORQXOX14SNnsS/N1bvVSBs1yfLtRosM+n/zayQAvY2oyB3GzQyVkC53BrVK5WwQkhUp21e6NYArlmrH0XKe5XkR2tVBu0lAH7uRFmJvVHJlZ4K/4/A2tGFFchlUQkl2U4S5wqm3dd0zqvLbbmcEXTeEpEcIgwPLHIafPEa2zeuqY6r82xfrC2/eamHIFQa9Ko7emmqMRwXWhk5trslE1lscsTs+HkplEqIxJCw51WnTEirTIjf2clbOcAiYY32U/wGsTAKL1L6rvjMfJOypd21TAnM3REX4tEFeJqEEHfz4zTRujlXCLYaT+Y193bI1Jgr+TLQaaQpM/1UoNZGUKpULx6WdzAPrRbymDaA5H4kiXw6RWjK3JfypZ20p6J9vaSY6lM6yTcl9vhrWgZeBE9RW3s2ufNRqm57yD51yQ2DTHW4KTLwqG150+8ktNyDRtwCDGHKyUAn62WHacrL8L0iKj1Fo/Z7LGg50RyKH3+CRMqgND3Kw9jSeq3JA1MvEuXzKUGswFm6sXgBZh4SaHBZ7gEzpXx/4CryCJSg9/ji3m4iPcJrpUO0D8bVAf9iRvExoO4f+brBwL+c0ymHAt6XkiSQqrOnGswFoT6T2NC31PGKJWxSirLhPhvEtifYYw/zL7SBP9BFmdf5hc2LdhIj99QkFyaJ/m3IJnwCj0ONt5gJt85jvDbIDj4SsEsIaC7tRqQT4hpMfVHJQFO6xQ0vBL4EIA1zYV+5M/Dkmn5UmJEqWIDOU/AZiZ8gq2VDFLv8jTavndYAoWQ2qpYplRGK9CjIvns+2QIOMdYhUMw1gKtCn3zb0JgAGYzE3C+IBh1Up9jKMGnPkMTvAKzCP32RJHUTFDkceR/ATzlBB4AAQAAAAEaoLKse0pfDzz1AB8IAAAAAADSfZgxAAAAANJ9mDH/x/6KCBgF5AAAAAgAAgAAAAAAAHicY2BkYGCP/JfEwMCh9v84kJRgAIqgADMAZcoEDQAAeJwVjC9rQlEYxn875z3Hg8gNhiGosCA2g5hkiMUg4gcwiGFxaVwMl8GiLAzDyk1GLYsnGez3IxgNq/sGIvO94eF5nt/7x/wxefgC90bV9YjunaX3RDmoPrVftNeIZs3Zbhm5lEJaxMqSWHL/wkp+KUp3XZ2NeJYjbXdmrz9D6JK4jioQ5MZCEt3P2NkTc/WZ9JmbSFMeGUhKbmpsTPgvlO80//hv8pKrZvKqrjd2SG4zxuZKT/mHNKj7JxIJtDUnNjK9AyQsLMUAAAAAAAAAKwBrALEA3QD0AQkBVgFtAXkBnwHYAecCIQJHAocCrwL8AzQDhgOYA8AD5QQtBGIEigSlBPcFLwVtBacF6QYYBpYGuQbgBxwHUQddB5oHwAf1CC4IaAiNCN4JEgk5CV4JqwnfCgoKKAAAeJxjYGRgYDBjiGVgZgABEI+JAQkAAA92AJsAAAAAAAIAHgADAAEECQABAAgAAAADAAEECQACAA4ACABMAGEAdABvAFIAZQBnAHUAbABhAHJ4nGNgZgCD/4UMVQxYAAAsjgHuAA==") format("woff");
  /* ... */
}

此代码看起来很吓人,但无需害怕。这里最困难的部分是生成数据 URI,并且 CSS-Tricks 在这里为我们提供了帮助

使用预加载的关键 FOFT

在此变体中,我们使用带有 preload 标记的链接添加到关键字体文件。以下是它的样子

<link rel="preload" href="path-to-roman-subset.woff2" as="font" type="font/woff2" crossorigin>

我们应该只预加载一个字体。如果我们加载多个,可能会对初始加载时间产生不利影响

策略列表中,Zach 还提到他更喜欢使用数据 URI 而不是 preload 变体。他之所以更喜欢它,是因为 preload 的浏览器支持过去很糟糕。今天,我认为浏览器的支持已经足够好,可以选择预加载而不是数据 URI。

此浏览器支持数据来自 Caniuse,其中包含更多详细信息。数字表示浏览器从该版本及更高版本开始支持该功能。

桌面

ChromeFirefoxIEEdgeSafari
50857911.1

移动/平板电脑

Android ChromeAndroid FirefoxAndroidiOS Safari
12712712711.3-11.4

Zach 的最后说明

Chris 通过 Zach 运行了这篇文章,Zach 希望他在他的 原始文章中优先考虑无 JavaScript 的方法。

我认为这篇文章很好,但我认为它所基于的文章在某些方面可能有点过时了。我希望当您只使用一个或两个字体文件(或更多,但每个字体类型仅 1 或 2 个)时,它能更优先考虑无 JS 方法。我认为 JS 方法现在有点像例外情况(除非您使用大量字体文件或云提供商不支持 font-display: swap)。

这稍微改变了结论,我将在下一节中进行总结。

使用哪种字体加载策略?

如果您使用云托管提供商

  • 如果主机提供,请使用 font-display: swap
  • 否则,使用使用类进行 FOUT。

如果您托管您的网页字体,您有几个选择

  1. @font-face + font-display: swap
  2. 带类名的FOUT
  3. 标准FOFT
  4. 关键FOFT

如何选择它们

  • 选择@font-face + font-display: swap,如果你刚开始接触,并且不想使用JavaScript。这是最简单的一种方法。如果你每个字体只使用少量字体文件(少于两个文件),也选择此选项。
  • 选择标准FOFT,如果你准备使用JavaScript,但不想额外做罗马字体的子集化工作。
  • 选择关键FOFT变体,如果你想为了性能做到极致。

就是这样!希望你发现所有这些内容都有用!

如果你喜欢这篇文章,你可能会喜欢我写的其他文章。考虑订阅我的时事通讯😉。此外,如果你有任何问题,请随时联系我。我会尽力帮助你!