`font-display` 为大众

Avatar of Jeremy Wagner
Jeremy Wagner

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

2017 年 1 月 12 日更新: 文章正文中现已包含适当的支持检查。添加了有关block 值的信息。进行了一些细微调整和文案编辑。享受!

如果您是 CSS-Tricks 的常客,那么您很有可能对网络字体有所了解。您甚至可能知道一些控制字体加载方式的有用技巧,但您是否使用过 CSS 的font-display 属性 ?

CSS 中的font-display 属性在 Chrome 中可用,并在 Firefox 和 Safari 中出现 (但您可能希望自己检查一下浏览器支持情况),因为事情一直在发生变化。它是一种更简单的方法来实现 字体加载 API 的功能,以及第三方脚本,例如 Bram Stein 的 Font Face Observer

如果您对这些内容还不太熟悉,不用担心。让我们首先谈谈浏览器中默认的字体加载行为。

浏览器中的字体加载

浏览器是精心设计的程序,它们在幕后做了很多我们可能没有意识到的工作。字体加载也不例外。当浏览器从 Web 服务器请求字体资产时,任何使用该字体的样式元素都会被隐藏,直到字体资产下载完成。这被称为“不可见文本闪烁”或 FOIT。

FOIT 效果的实际应用

这种行为是为了“避免”我们看到文本最初使用系统字体呈现,然后再切换到自定义字体(一旦加载完成)。这种行为被称为“未设置样式的文本闪烁”或 FOUT。

FOIT 听起来可能比 FOUT 更可取,但 FOIT 会对缓慢连接的用户造成影响。默认情况下,大多数浏览器会在显示系统字体作为回退,同时等待字体加载之前,将文本隐藏长达 3 秒。其他浏览器,例如 Safari,会等待更长时间,甚至无限期等待,导致一些用户遇到文本不可见,并且如果字体请求停滞,文本可能无法呈现。

我们可以使用基于 JavaScript 的解决方案来解决这个问题,以跟踪字体何时加载。我们将文档的样式设置为默认使用系统字体,然后,当我们检测到自定义字体已加载时,我们将向文档添加一个 CSS 类,该类会依次将自定义字体应用于文档。这种方法以前就讨论过。例如,假设您有一个使用 Open Sans Regular 作为所有<p> 元素的页面的页面。最初,您将使用以下类似的 CSS 代码

p {
  font-family: "Helvetica", "Arial", sans-serif;
}

在字体请求正在进行时,Helvetica 或 Arial(取决于系统上可用的字体)将首先显示。当我们通过 JavaScript 验证 Open Sans Regular 已加载后,我们将向<html> 元素应用一个fonts-loaded 类,该类会通过以下 CSS 代码将 Open Sans 应用于<p> 元素

.fonts-loaded p {
  font-family: "Open Sans Regular";
}

这些解决方案有效,但它们可能非常笨拙。这就是 CSS 的font-display 属性发挥作用的地方。

认识font-display

font-display 是从 Chrome 61、Chrome for Android、Opera 和 Safari 的技术预览版开始可用的 CSS 属性。Firefox 58 即将支持此属性。使用font-display,我们可以控制字体的呈现方式,就像使用基于 JavaScript 的解决方案一样,只是现在通过一行方便的 CSS 代码!font-display 用于@font-face 中,并接受以下值

  • auto:默认值。将执行典型的浏览器字体加载行为。这种行为可能是 FOIT,也可能是 FOIT,但不可见时间相对较长。这可能会随着浏览器供应商决定更好的默认行为而发生变化。
  • swap:回退文本将立即在字体堆栈中下一个可用的系统字体中呈现,直到自定义字体加载,此时将使用新字体进行替换。对于正文文本,我们希望看到这种行为,因为我们希望用户能够立即阅读内容。
  • block:与 FOIT 类似,但不可见时间会无限期持续。在任何情况下,如果无限期阻塞文本呈现更可取,请使用此值。在大多数情况下,block 并不比其他任何值更可取。
  • fallbackblockswap 之间的折衷方案。自定义字体样式的文本将不可见很短的时间(100 毫秒 根据 Google 的说法),然后如果自定义字体在短阻塞时间过后没有加载,则将显示未设置样式的文本。字体加载后,文本将以适当的方式设置样式。当 FOUT 不可取但无障碍更重要时,这非常有用。
  • optional:与fallback 相似,因为它会使受影响的文本最初不可见一小段时间,然后如果字体资产尚未完成加载,则过渡到回退。但相似之处到此结束。optional 设置让浏览器可以自由决定是否应该使用字体,这种行为取决于用户的连接速度。如果使用此设置,则在连接速度慢的情况下,您应该预期自定义字体可能根本无法加载。

现在我们知道了font-display 接受的值,可以将其应用于 @font-face 规则。以下是在@font-face 中使用swap 值的 Open Sans Regular 示例

@font-face {
  font-family: "Open Sans Regular";
  font-weight: 400;
  font-style: normal;
  src: url("fonts/OpenSans-Regular-BasicLatin.woff2") format("woff2");
  font-display: swap;
}

当然,我们在本示例中通过只使用 WOFF2 文件来缩写@font-face,但我的目的是简洁。在本示例中,我们使用swap 选项作为font-display,它会产生与以下图像中所示的加载行为类似的行为

font-display 属性的 swap 值的实际应用。

当我们在 JavaScript 中控制字体加载时,这通常是我们想要的行为。我们希望确保文本默认可见,然后在自定义字体下载后应用自定义字体。

但什么是“回退文本”?当您为font-family 属性指定值时,您需要使用逗号分隔的字体名称列表进行指定。这被称为字体堆栈。回退是在主字体(可能是自定义字体)之后的字体堆栈中的下一个系统字体。

p {
  font-family: "Open Sans Regular", "Helvetica", "Arial", sans-serif;
}

在本示例字体堆栈中,自定义字体是 Open Sans Regular。系统字体是 Helvetica 和 Arial。使用font-display: swap; 时,最初显示的字体是堆栈中的第一个系统字体。当自定义字体加载后,它将启动并替换最初显示的系统字体。使用font-displayfallbackoptional 也将在必要时依赖堆栈中的系统字体。

大多数情况下,您将使用swap

如果您不知道使用哪个选项,请使用swap。它允许您使用自定义字体,向无障碍性发出提示。如果您使用的是“很好,但可以省略”的字体,请考虑指定optional

如果font-display 不受支持怎么办?

就像所有新出现的浏览器功能一样,font-display 的支持并非无处不在。因此,您在使用它时有两个选择

  1. 您可以直接使用font-display,仅此而已。如果其他浏览器不支持它,它们将按照其默认行为运行。这并不一定不好,因为它不会破坏任何东西,但它可能并非最适合您。
  2. 您可以检测font-display 的支持情况,并提供替代方案。如果时间和资源允许,请考虑这样做。

如果您决定使用选项 2,则需要检测font-display 的支持情况。这可以通过 此 fiddle 中所示的方式实现(从 此 Github 讨论 中发现)。

try {
  var e = document.createElement("style");
  e.textContent = "@font-face { font-display: swap; }";
  document.documentElement.appendChild(e);
  var isFontDisplaySupported = e.sheet.cssRules[0].cssText.indexOf("font-display") != -1;
  e.remove();
} catch (e) {
  // Do something with an error if you want
}

从这里开始,我们可以根据isFontDisplaySupported 变量的值来决定该做什么。您可以使用此功能检查的一种方法是,如果isFontDisplaySupportedfalse,则回退到字体加载 API,如下所示

if (isFontDisplaySupported === false && "fonts" in document) {
  document.fonts.load("1em Open Sans Regular");
  document.fonts.ready.then(function(fontFaceSet) {
    document.documentElement.className += " fonts-loaded";
  });
}
else {
  // Maybe figure out your own strategy, but this might be sensible:
  document.documentElement.className += " fonts-loaded";
}

在本示例中,如果isFontDisplaySupportedfalse,则字体加载 API 会处理过渡。一旦 API 了解字体已加载,我们就可以将fonts-loaded 类应用于<html> 标签。应用此类后,我们就可以编写 CSS 代码,允许逐步应用自定义字体

p {
  font-family: "Helvetica", "Arial", sans-serif;
}

.fonts-loaded p {
  font-family: "Open Sans Regular";
}

当然,我们更希望使用像 `font-display` 这样的 CSS 单行代码来完成所有这些工作,但至少我们可以在必要时回退到其他解决方案。随着时间的推移,`font-display`(以及 Font Loading API)可能会在大多数(如果不是全部)浏览器中实现。

第三方字体提供商呢?

如果您使用 Google Fonts 或 TypeKit 等第三方服务嵌入字体,目前您无能为力。第三方服务控制其托管的 `@font-face` 的内容,因此您可能需要考虑自行托管字体(我在本文中对此进行了讨论)。

随着时间的推移,提供商可能会在您从其服务中检索嵌入代码时将 `font-display` 设为可配置选项。`font-display` 正在积极讨论,以期在 Google Fonts 中实现,但不要假设每个第三方服务都会争先恐后地实现它。

更新:Google Fonts 现在默认使用 `swap`。万岁!

无论如何,`font-display` 是网页排版领域中一个受欢迎的补充。它极大地简化了在 JavaScript 中原本很繁琐的任务。自己尝试一下 `font-display`,看看它能为您和您的用户做些什么!


Cover of Web Performance in Action

Jeremy Wagner 是 Manning Publications 出版社的《网页性能实战》一书的作者。使用优惠券代码 `sswagner` 可享受 42% 的优惠!

在 Twitter 上关注他:@malchata