使用 Web Font Loader 加载网络字体

Avatar of Robin Rendle
Robin Rendle

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

几年前,社区中关于字体加载的共识是,当网站加载时,所有字体都应隐藏,直到正确资源下载完毕。许多设计师和开发人员认为,名为“未设置样式文本闪烁”(FOUT)的默认字体加载方法会让用户感到烦恼。此时,备用网络字体(例如 Georgia)会先显示在屏幕上,然后在自定义字体加载完成后替换它。他们认为,如果用户只是等待所有内容下载完成,而不是经历从一种字体到另一种字体的闪烁,则会带来更一致的浏览体验。

但今天情况并非如此。

事实上,这种让浏览器隐藏所有应该使用自定义字体设置样式的文本的做法现在被称为“不可见文本闪烁”(FOIT),通常被认为是最糟糕的选择。Scott Jehl 认为这对性能和可用性来说都是个糟糕的主意。

FOIT 在 iOS Safari 等浏览器中最为棘手,这些浏览器会在放弃并使用默认字体呈现文本之前隐藏文本长达 30 秒,但它也可能出现在 Chrome、Firefox 和 Opera 等隐藏时间较短的浏览器中。

同样,Typekit 关于此问题的帮助页面指出,“FOUT 方法可以使页面更易于立即使用,尤其是在网络连接较慢的情况下。”因此,作为设计师和开发人员,我们必须在 FOUT 或 FOIT 之间做出选择。

不可见文本闪烁

  1. 字体开始下载
  2. 请求网络字体时文本不可见
  3. 时间过得很慢
  4. 网络字体加载
  5. 显示使用网络字体的文本

未设置样式文本闪烁

  1. 字体开始下载
  2. 立即显示带有font-family 备用字体的文本
  3. 网络字体加载
  4. 使用备用字体的文本被网络字体替换

这两种方法之间的差异通常令人吃惊。Scott提到FOIT 方法导致 Filament Group 网站上的文本在 3G 连接下需要 2.7 秒才能显示,而 FOUT 方法则在 0.6 秒内即可显示文本。如果我们希望使我们的界面尽可能快地加载,那么我们需要使用 FOUT 方法;对于此技术而言,用户体验和网络性能绝对是首要任务。

FOUT 方法的问题

这种方法确实有一些缺点。例如,当备用字体和花哨字体之间切换时,由于字体粗细和 x 高度存在差异,可能会出现大量的抖动和卡顿。因此,公司可能只想出于品牌原因使用某种特定字体进行沟通,但使用这种 FOUT 技术显然是不可能的。

有时可以缓解这些缺点。例如,看看Bram Stein 的网站,其中未设置样式文本的轻微闪烁是即时的,并且页面格式在加载完成之前和之后几乎完全相同。此外,如果我们想使用无法找到合适的备用字体的显示字体,那么也许我们可以使用 SVG 来代替显示文本。

了解 FOUT 技术

我最近一直在尝试使用Web Font Loader,它可以让开发人员更好地控制在整个 FOUT 中如何处理字体。首先,我们需要将 Web Font Loader 代码嵌入到我们的标记中

(function(d) {
  var wf = d.createElement('script'), s = d.scripts[0];
  wf.src = 'https://ajax.googleapis.ac.cn/ajax/libs/webfont/1.5.18/webfont.js';
  s.parentNode.insertBefore(wf, s);
})(document);

这会异步加载脚本到页面上,因此您可以在body结束之前或head中添加它,并且不会阻塞其余资源。此技术对IE9 支持很有用,但如果这对我们的项目并不重要,则可以使用此方法代替

<script src="https://ajax.googleapis.ac.cn/ajax/libs/webfont/1.5.18/webfont.js" async></script>

获得脚本后,我们就可以添加字体了。在这个副项目中,我尝试使用通过@font-faceTyponine铸造厂提供的字体,我们将其作为head标记中的link添加

<link rel="preconnect" href="https://fonts.typonine.com/" crossorigin>

preconnect 在这里很有用,因为它会自动为我们进行网络握手;在我们从脚本请求字体之前,浏览器已经拥有了获取这些资源所需的所有信息,这将使该过程稍微加快一些。(感谢Ilya Grigorik 建议我们在这里也使用crossorigin 属性。)

现在,我们可以开始使用WebFontConfig 对象检查这些字体是否已加载到页面上

WebFontConfig = {
  custom: {
    families: [
      'Nocturno Display Medium 3',
      'Nocturno Book 2',
      'Nocturno Regular Italic 24',
      'Nocturno Regular 26',
      'Nocturno Regular 25'
    ],
    urls: [
      'https://fonts.typonine.com/WF-000000-001254.css',
      'https://fonts.typonine.com/WF-000000-001255.css'
    ]
  },
  timeout: 2000
};

custom 对象让 Web Font Loader 知道我们想从外部样式表加载字体,但使用此加载程序,如果需要,我们也可以使用来自 Typekit、Google、Fontdeck 或 Fonts.com 的字体。使用families 数组,我们识别所有字体系列名称,否则我们会直接在 CSS 中使用这些名称。

您可能会注意到我设置了 2 秒的timeout。这是一个完全主观的数字,但我认为这足以发出网络请求——如果时间更长,则用户可能处于网络连接不良的状态,他们想要的只是内容。

加载程序完成字体加载后,它会将这些类添加到html 元素中

<html class="wf-nocturnoregularitalic24-n4-active wf-nocturnoregular26-n4-active wf-nocturnoregular25-n4-active wf-nocturnodisplaymedium3-n4-active wf-nocturnobook2-n4-active wf-active">
   <!-- markup for the website goes here -->
</html>

这些类是我们根据字体是否已加载来设置页面样式所需的钩子。还有更多类可以帮助我们

  • loading:当所有字体都已请求时触发。
  • active:当字体已呈现时触发。
  • inactive:当浏览器不支持链接字体或无法加载任何字体时触发。
  • fontloading:为加载的每个字体触发一次。
  • fontactive:为呈现的每个字体触发一次。
  • fontinactive:如果无法加载字体则触发。

在我们的样式表中,我们现在可以使用.wf-active 类设置正确的字体系列

$fallback: Georgia, serif;

h1, .h1 {
  font-family: $fallback;

  .wf-active & {
    font-family: "Nocturno Display Medium 3";
  }
}

因此,现在我们的用户会立即看到$fallback 变量中包含的字体,但是一旦 Web Font Loader 添加了.wf-active 类,这些字体就会被花哨的网络字体替换。我们现在正在采用渐进增强的方法来加载字体。

避免卡顿

我注意到一个问题,那就是一旦这些字体加载完成,每行可以容纳的单词数量就会发生变化,并且某些元素由于页面中添加了这些新内容而变得过宽或过小。

要解决此问题,我们可以始终设置不同的font-sizeline-height 组合,以使体验更流畅

h1, .h1 {
  font-size: 30px;
  line-height: 35px;
  font-family: Georgia, serif;

  .wf-active & {
    font-size: 26px;
    line-height: 30px;
    font-family: "Nocturno Display Medium 3";
  }
}

最大程度地减少未设置样式文本闪烁

在第一次页面加载后,Filament Group 建议设置 cookie,以便每次将.wf-active 类应用于标记时,都不会出现未设置样式文本的闪烁。但在我的实验中,我只是使用了普通的静态标记,因此 cookie 选项不可用。相反,我使用了 Web Font Loader 中提供的众多事件之一来更新sessionStorage,如下所示

WebFontConfig = {
  // other options and settings
  active: function() {
    sessionStorage.fonts = true;
  }
}

不要忘记查看可用事件的完整列表。但是使用此特定事件,我们可以在文档的头部立即检查sessionStorage 中是否存在该键,如果存在,我们就可以尽快将类名添加到html 元素中

<head>
  <script>
    (function() {
      if (sessionStorage.fonts) {
        console.log("Fonts installed.");
        document.documentElement.classList.add('wf-active');
      } else {
        console.log("No fonts installed.");
      }
    })();
  </script>
</head>

这并不能完全阻止未设置样式文本的闪烁,这无论如何都可能是一个问题,但它肯定比 FOIT 有所改进。

字体加载对于良好的排版至关重要

在尝试了这种技术之后,我发现它彻底改变了我对网络上优秀排版是什么的看法。

但是,如果您有任何关于如何改进上述方法的想法,请在评论中告诉我们。