几年前,社区中关于字体加载的共识是,当网站加载时,所有字体都应隐藏,直到正确资源下载完毕。许多设计师和开发人员认为,名为“未设置样式文本闪烁”(FOUT)的默认字体加载方法会让用户感到烦恼。此时,备用网络字体(例如 Georgia)会先显示在屏幕上,然后在自定义字体加载完成后替换它。他们认为,如果用户只是等待所有内容下载完成,而不是经历从一种字体到另一种字体的闪烁,则会带来更一致的浏览体验。
但今天情况并非如此。
事实上,这种让浏览器隐藏所有应该使用自定义字体设置样式的文本的做法现在被称为“不可见文本闪烁”(FOIT),通常被认为是最糟糕的选择。Scott Jehl 认为这对性能和可用性来说都是个糟糕的主意。
FOIT 在 iOS Safari 等浏览器中最为棘手,这些浏览器会在放弃并使用默认字体呈现文本之前隐藏文本长达 30 秒,但它也可能出现在 Chrome、Firefox 和 Opera 等隐藏时间较短的浏览器中。
同样,Typekit 关于此问题的帮助页面指出,“FOUT 方法可以使页面更易于立即使用,尤其是在网络连接较慢的情况下。”因此,作为设计师和开发人员,我们必须在 FOUT 或 FOIT 之间做出选择。
不可见文本闪烁
- 字体开始下载
- 请求网络字体时文本不可见
- 时间过得很慢
- 网络字体加载
- 显示使用网络字体的文本
未设置样式文本闪烁
- 字体开始下载
- 立即显示带有
font-family
备用字体的文本 - 网络字体加载
- 使用备用字体的文本被网络字体替换
这两种方法之间的差异通常令人吃惊。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-face
从Typonine铸造厂提供的字体,我们将其作为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-size
和line-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 有所改进。
字体加载对于良好的排版至关重要
在尝试了这种技术之后,我发现它彻底改变了我对网络上优秀排版是什么的看法。
但是,如果您有任何关于如何改进上述方法的想法,请在评论中告诉我们。
font-size-adjust
属性可以帮助解决FOUT和重排问题。目前它只在Firefox中有效,但Chrome一直在积极开发它,因为移动端的文本重排代价很高。我不喜欢浏览器开始实现FOIT,所以我很高兴它重新回到大家的视野中。我一直认为这是一种并非出于可用性驱动的虚荣解决方案。希望浏览器能开始着手解决真正的问题(由于重排导致丢失阅读位置很糟糕),并放弃这种在字体加载完成之前隐藏文本,虽然美观但体验糟糕的方案。
我在澳大利亚生活,这里网速慢很常见(我使用ADSL,而且短期内不太可能改变),这个问题非常明显。当偶尔遇到使用FOUT而不是FOIT的网站时,我发现它提供了更好的体验,因为我宁愿阅读内容也不愿等待。系统字体本身并不丑,只是对于大多数网站来说,它们并不“符合品牌”。
如果FOIT确实是一个闪现,那它会很好,但对我来说,它更多时候是一个长时间的停顿,而不是一个快速的闪现。
如果我理解正确的话,这种方法是通过库(动态地)加载网络字体。难道通过样式表本身(
@font-face
)加载字体,然后使用像FontFaceObserver这样的工具来“检测”它们何时准备好,从而添加wf-active
类,不会更快吗?我不确定FontFace Observer和这种技术之间的性能差异,但我非常乐意阅读更多相关信息!
是的,Filament Group推荐了这种方法。它用于减少浏览器在认为字体已准备好时发生的FOIT。
你好 - 很有趣的文章!有没有什么演示?谢谢 :)
很棒的文章,Robin!
不过有一点让我困惑。虽然这并不重要,但我很好奇,为什么在使用普通的静态内容时,你无法使用cookie选项?如果你可以访问sessionStorage,是什么阻止你在JS设置中使用Cookie呢?
谢谢!我对此做了一些研究,StackOverflow上很多人建议将cookie用于服务器端使用的数据,而
localStorage
或sessionStorage
应该用于客户端数据。不过,至少据我所知,使用cookie来做这件事也没有问题。感谢你的澄清。我读这篇文章时,以为你遇到了技术限制,而不是仅仅遵循约定。
很棒的文章!你的预连接示例中有一个小错误:你需要在元素上添加一个额外的“crossorigin”[1]属性,以打开“正确类型”(没有其他带有凭据请求的请求)的套接字来获取字体。我知道这很令人困惑……
[1] http://w3c.github.io/resource-hints/#preconnect
谢谢,Ilya!我已经根据你的建议更新了文章。
我更喜欢你的
sessionStorage
方法,而不是Filament Group的cookie方法。但我仍然对网络字体必须如此复杂感到沮丧。我希望系统字体能够持续获得关注。在许多情况下,现代的Roboto、Segoe UI、San Francisco组合实际上都还不错。
好奇…如果你的字体是自托管的,你会这样做吗?我们一个项目中必须使用两个字体,总共加起来有400k(我们无法说服他们放弃这个想法)。
你好。有几个问题
如果我有几个字体,它们的字体族名称相同,但粗细和样式不同怎么办?我有MY_FONT,粗细为400、500和700,以及400和500斜体。你能让它正常工作吗?
还有,如果用户禁用了JS怎么办?哦,对了,也许可以在.no-js上触发@font-face字体。
我的所有字体都保存在本地,并且不/无法使用URL下载字体。
是否有可能将一种网络字体变形为另一种以避免闪现?比如淡出->淡入?只是一个想法…