字体加载优化
关于 preload、prefetch 与 font-display
一些前置知识
FOIT
有时为了更好的展示效果,不得不引入第三方字体。而网络差或字体文件过大的时候,就会出现下图这种十分影响用户体验的“闪烁”现象,称之为 FOIT(Flash of Unstyled Text)。
而另外一种特殊的“字体”,图标字体则表现为下图这样的矩形。
以本博客使用到的字体图标为例
字体显示时间轴
参照 MDN 字体显示时间轴,浏览器渲染字体分为三个周期。
阻塞期(Block Period)。如果字体没有加载完成,浏览器会使用后备字体进行渲染,但是显示为空白,即用户不可见;如果在此期间字体已成功加载,则正常使用它。
交换期(Swap Period)。跟阻塞期类似,如果字体没有完成加载,则使用后备字体,但这时会渲染出来,即用户可见。
失败期(Failure Period)。如果字体加载失败,则使用后备字体。
简单地说,用户本地环境中没有设定的字体,需要从网络上下载,且在阻塞期(一般为3秒)没能下载完成,就会使用备用字体来显示文字,等待下载完成后,再替换字体。这就是上图中字体闪烁的原因。
解决方法
从上可见,如果字体能在阻塞期加载完成,就不会出现闪烁。而可控因素无非两个:
字体文件在瀑布流中的加载顺序太靠后
字体文件过大
preload 与 prefetch
preload
关于浏览器资源加载优先级,以下面的这个简单的 demo 为例
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>preload</title>
<script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.0/jquery.js"></script>
<link href="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/4.6.1/css/bootstrap.css" rel="stylesheet">
<style>
@font-face {
font-family: 'writting';
src: url('../fonts/writting.ttf');
font-display: swap;
}
body {
font-family: 'writting';
}
</style>
</head>
<body>
<div class="text-center">
<p>登鹳雀楼</p>
<p>王之涣</p>
<p>白日依山尽,黄河入海流。</p>
<p>欲穷千里目,更上一层楼。</p>
</div>
<div class="text-center"><img width="500px" src="./images/IMG_20200809_152622.jpg" alt=""></div>
</body>
</html>
上面这个例子中,有CSS、JS、图片、字体
从开发者工具——网络——瀑布流中可见,字体加入队列的默认顺序是比较靠后的
在<head></head>
标签里添加这样一行,就可以提升他的加载优先级
<link rel="preload" href="./fonts/writting.ttf" as="font" type="font/ttf" crossorigin>
在实际的开发中,引入的各种CSS、JS等资源,远不止 demo 中那样只有一个两个,如果字体文件并没有多大,只是处于队列的后面造成 FOIT,那这个方法将会非常见效。
preload 可以预加载的类型
audio: 音频文件
document: 嵌套在 <frame>
或 <iframe>
里的文档
embed: 嵌套在 <embed>
里的资源
fetch: 通过 fetch 或者 XHR request 获得的资源, 例如 ArrayBuffer 或 JSON 文件
font: 字体文件
image: 图片文件
object: 嵌套在 <object>
元素里的资源
script: JavaScript 文件.
style: CSS 样式表
track: WebVTT 文件.
worker: JavaScript web worker 或 shared worker.
video: 媒体文件
几乎就是你没想到的资源都能通过 preload 预加载
preload 使用方法
preload 作为 <link>
的 rel 属性,使用 as
来指定加载类型几个例子:
<link rel="preload" href="style.css" as="style">
<link rel="preload" href="main.js" as="script">
另外,<link>
标签还可以接受一个 type 属性,用于指定 MIME 类型。如果浏览器支持这个 MIME 类型,就会加载他,否则就忽略它。像是上述例子中的字体类型,以及下面这个媒体类型
<link rel="preload" href="XXX.mp4" as="video" type="video/mp4">
prefetch
利用浏览器空闲时间来下载下一个页面
可能访问的资源,也就是说该方式的作用是加速下一个页面的加载速度。
<link rel="prefetch" href="XXX.png">
对比与小结
相同点:都会提前加载指定的资源(无论这个资源有没有被当前页面使用)
不同点:
preload 用于提升资源的优先级,用于当前页面
prefetch 会降低资源的优先级,用于下一个打开的页面
font-display
对于体积较小的字体,使用 preload 提升字体文件的加载优先级,大概率能在交换期完成下载,以几秒钟的空白页为代价我觉得是可以接受的。对于英文字体与图标字体而言,这通常是有效的,因为英文就26个字母,外加一些符号,一般也就不到1M;而中文字体本身比较复杂,小则几M,大则几十M,这时候就算他在队列的最前面也没用。当然网上有工具可以提取字体文件中特定的一些文字,如果不需要经常改变这些特定文字,这个方法是推荐的。但总会有需要完整引入整个字体文件的情况,这时能做的就只有尽量改善用户体验。
所以出现了 font-display
这个属性,如果设定的字体还未可用,一共有五种属性值:
auto:使用浏览器默认的行为。
block:短暂的阻塞周期和无限的交换周期。先使用隐形文字,阻塞期后(通常为3s)使用备用字体,直到字体加载完成再替换。
swap:非常小的阻塞周期和无限的交换周期。与 block 相似,但会直接先使用备用字体(非常小的阻塞周期),直到字体加载完成再替换。
fallback:非常小的阻塞周期和短暂的交换周期。与 swap 相似,但限制加载时长(短暂的交换期),一旦加载所需的时长大于这个限制(Webkit 和 Firefox 中设定此时间为 3s),设定的字体将不会替换备用字体。
optional:非常小的阻塞周期,并且没有交换周期。如果设定的字体没有在阻塞期内加载完成,则当前页面会一直使用备用字体,但会把字体缓存下来,以便下次直接使用。
使用方法,以上述 demo 为例
@font-face {
font-family: 'writting';
src: url('../fonts/writting.ttf');
font-display: swap;
}
看着很复杂,可以总结为以下四种情况:
block:隐形–备用–指定字体
swap:备用–指定字体
fallback:备用–指定字体,但如果交换期内还没加载完,将不会替换新字体
optional:与 fallback 一样,但会缓存字体下次使用
至于使用哪个属性值,就要根据需求来选择了。如果3s内大概率能加载完成,那就选block;如果效果不重要想先用默认字体显示内容,那选swap;如果不觉得两次访问同一网站会显示不同字体会对用户造成困惑,optional 也是可以的。
留言