2017 年 1 月 12 日更新: 文章正文中现已包含适当的支持检查。添加了有关block
值的信息。进行了一些细微调整和文案编辑。享受!
如果您是 CSS-Tricks 的常客,那么您很有可能对网络字体有所了解。您甚至可能知道一些控制字体加载方式的有用技巧,但您是否使用过 CSS 的font-display
属性 ?
CSS 中的font-display
属性在 Chrome 中可用,并在 Firefox 和 Safari 中出现 (但您可能希望自己检查一下浏览器支持情况),因为事情一直在发生变化。它是一种更简单的方法来实现 字体加载 API 的功能,以及第三方脚本,例如 Bram Stein 的 Font Face Observer。
如果您对这些内容还不太熟悉,不用担心。让我们首先谈谈浏览器中默认的字体加载行为。
浏览器中的字体加载
浏览器是精心设计的程序,它们在幕后做了很多我们可能没有意识到的工作。字体加载也不例外。当浏览器从 Web 服务器请求字体资产时,任何使用该字体的样式元素都会被隐藏,直到字体资产下载完成。这被称为“不可见文本闪烁”或 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
并不比其他任何值更可取。fallback
:block
和swap
之间的折衷方案。自定义字体样式的文本将不可见很短的时间(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
,它会产生与以下图像中所示的加载行为类似的行为

当我们在 JavaScript 中控制字体加载时,这通常是我们想要的行为。我们希望确保文本默认可见,然后在自定义字体下载后应用自定义字体。
但什么是“回退文本”?当您为font-family
属性指定值时,您需要使用逗号分隔的字体名称列表进行指定。这被称为字体堆栈。回退是在主字体(可能是自定义字体)之后的字体堆栈中的下一个系统字体。
p {
font-family: "Open Sans Regular", "Helvetica", "Arial", sans-serif;
}
在本示例字体堆栈中,自定义字体是 Open Sans Regular。系统字体是 Helvetica 和 Arial。使用font-display: swap;
时,最初显示的字体是堆栈中的第一个系统字体。当自定义字体加载后,它将启动并替换最初显示的系统字体。使用font-display
值fallback
和optional
也将在必要时依赖堆栈中的系统字体。
swap
。
大多数情况下,您将使用如果您不知道使用哪个选项,请使用swap
。它允许您使用自定义字体,并向无障碍性发出提示。如果您使用的是“很好,但可以省略”的字体,请考虑指定optional
。
font-display
不受支持怎么办?
如果就像所有新出现的浏览器功能一样,font-display
的支持并非无处不在。因此,您在使用它时有两个选择
- 您可以直接使用
font-display
,仅此而已。如果其他浏览器不支持它,它们将按照其默认行为运行。这并不一定不好,因为它不会破坏任何东西,但它可能并非最适合您。 - 您可以检测
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
变量的值来决定该做什么。您可以使用此功能检查的一种方法是,如果isFontDisplaySupported
为false
,则回退到字体加载 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";
}
在本示例中,如果isFontDisplaySupported
为false
,则字体加载 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`,看看它能为您和您的用户做些什么!
添加 JS 条件来测试是否支持它不会抵消使用它的好处吗?
我不确定你的意思 - 你是指我文章中编写的功能检测代码不是 100% 可靠吗?如果是这样,那么在某些情况下可能会抵消。但是,如果功能支持能够被正确且可靠地检测到,那么任何 JavaScript 回退代码都只会 `font-display` 不可用时才会执行。
您可以为此使用功能查询 https://hacks.mozilla.ac.cn/2016/08/using-feature-queries-in-css/
我试过了,但没有效果:https://css-tricks.org.cn/font-display-masses/#comment-1603606
您能否使用 `@supports` 进行功能检测而不是 js?(并为其他人提供回退)?
当然可以!我唯一犹豫的是,功能查询和 `@supports` JS API 并非普遍支持。在这种情况下,我更喜欢一种单一、更广泛支持的方法来确定对 `font-display`(或任何功能)的支持。
有不支持 `@supports` 但支持 `font-display` 的浏览器吗?
说得对。但我用这段代码在 Chrome Canary 中用 JavaScript 检查时...
...我不确定 `supports` 是否按预期工作。使用 `CSS.supports` 检查 `unicode-range` 支持时,我也得到了类似的结果
因此,我不确定如何使用 `CSS.supports` 检查特定于 `@font-face` 的规则。我对使用 `supports` API 还比较陌生。使用功能查询语法会得到类似的结果
如果有任何人有想法,我很想听听,因为这将是检查任何 CSS 功能支持(更不用说 `font-display`)的更简单方法。
除非我漏了什么(我可能是漏了),否则它在 Canary 中不受支持(在源代码中编辑 CSS 文件时,`font-display` 没有语法高亮),而且我在标志中也找不到任何关于它的引用?Opera 也一样。虽然它建议两者都应该工作?https://developers.google.com/web/updates/2016/02/font-display?hl=en
尝试使用 `@supports`(CSS)或 `CSS.supports`(JS)进行测试,但结果依然和您一样,所有 3 个浏览器都报告为 false。非常令人困惑。
Brendan
大家好,
它不能与 CSS `supports` 一起使用。
我在此处提交了一个错误报告,结果证明是无效的,因为 Chromium 开发人员在以下链接中进行了解释
https://bugs.chromium.org/p/chromium/issues/detail?id=600137
“这实际上是按预期工作。`font-display` 是一个描述符,而不是一个属性,`CSS.supports` 仅用于属性(既有 `font-family` 描述符,也有 `font-family` 属性,因此它在 `CSS.supports` 中有效)。"
我想这说得通,很有趣。
嗨,Jeremy,
该 `font-display` 功能检测可能不起作用。它在 Chrome 中有效(启用后),但它不应该有效。
它在 Firefox 中不起作用(启用后),因为它应该不起作用。
就在今天,一位 Mozilla/Firefox 开发人员告诉我这是功能检测的方法
https://jsfiddle.net/p7g8y7du/
背景信息在此
https://bugzilla.mozilla.org/show_bug.cgi?id=1296373
感谢您提供这些信息!我的功能检测技术显然不是万无一失的。:)
Chris 已更新文章,以指向您指出的 GitHub 讨论。我没有权限更新文章,因此由 Chris 决定是否让我更新文章中的代码(如果他要求的话,我会更新)。
再次感谢!
我宁愿看到 FOIT 而不是 FOCF - 我认为字体变化的闪烁比文字从不可见变为可见更令人不安
确实,几年前,我们一直在努力寻找一种方法来避免 FOUT(未设置样式文字的闪烁),而这正是此处 `swap` 值创建的情况。我想所有过时的东西都会重新流行起来。
是的,当然,如果您使用的是快速连接。但是,如果不是,看到几秒钟(或更长时间)的未设置样式文字比什么都看不到要好。使用 FOUT,过渡更加“难看”,但网站可以更快地使用。
感谢您提供这篇文章,我之前没有意识到这种可能性,而且我一直想要类似的东西来避免透明文字。
关于外部提供商的问题,也许真正的答案是 - 避免使用它们?我一直更喜欢将字体和 CSS 复制到本地,避免对外部提供商(如 Google Fonts)发出任何请求,这样,代码的一部分就不会依赖于外部网站,而外部网站可以不受控制地对其进行更改。
我完全理解为什么有些人可能想要避免外部提供商,但这些提供商拥有非常深厚的资金,可以用于在全球范围内分发资源,以实现更快的交付。我认为任何值得信赖的第三方提供商最终都会允许配置此功能。
也就是说,您始终可以像您所说的一样做,但可以在您的网站前面放置一个 CDN 服务(如 Cloudflare),这将达到类似的效果。嗯,也许我需要为自己的网站做这个...
感谢您的阅读!
您是如何从“OMG,FOUT = 糟糕!”转变为它成为理想效果的?
可访问性。当浏览器长时间阻止文字渲染时,对于某些人来说,永久隐藏文字的可能性是不可接受的结果。
说得对,这来来回回的真是很有趣,不过肯定如此。
正如另一条评论中所说,所有过时的东西都会重新流行起来,但这一次是为了一个好理由。:) 感谢您的阅读,伙计。
嗨,Jeremy,
感谢您分享这篇文章,我希望浏览器很快就能提供对它的支持!
为什么浏览器不回退到 `font-family` 列表中的下一个条目?这似乎是解决问题的最明显方法。
例如
我设置了 `font-family: "Asap", Arial;`
我加载页面,网页字体仍在加载,因此 `Asap` 仍然不可用,浏览器应该回退到 Arial,一旦网页字体加载(可用),它应该切换到网页字体。
这难道不合理吗?
我很好奇为什么你不使用 classlist API 来管理 CSS 类?你在连接字符串……
如果我所做的只是在 HTML 元素中添加一个类,我可以通过简单地连接
fonts-loaded
类来实现最广泛的覆盖范围(classList
在全球范围内大约有 90% 的浏览器支持)。如果我需要切换/移除等操作,在这种情况下我会使用classList
。我完全承认,这是一种有点挑剔的做法。第三方字体公司怎么样?
如果你嵌入与第三方合作支持(如 Google Fonts 或 TypeKit)的字体,你会发现你无能为力。字体显示属性必须在 @font-face 声明中使用。因为你不能控制字体服务提供商提供的 CSS,所以你无法控制在这种情况下字体显示属性的存在,更不用说传递给它的值了。 http://www.iadmission.org/
所以还没有人找到一个可以同时解决 FOIT 和 FOUT 的解决方案吗?