有时候前端开发需要使用到一些特殊字体,但宿主机上一般都没有安装相应的字体,所以需要将字体文件与前端代码一起打包以及用 CSS 定义使用。本文主要是想回答一个问题:在性能方面,我们可以怎么去优化前端需要加载的字体?
一般优化的思路主要是两方面:
- 缩小字体文件
- 优化字体加载的方式
缩小字体文件
字体文件一般都比较大,动不动就几兆、十几兆的,所以我们优化的第一步是想办法缩减字体文件的大小。
使用特定的字体格式——WOFF/WOFF2
常见的字体格式有TTF(TrueType Font)、OTF(OpenType Font)、EOT(Embedded Open Type)、WOFF(Web Open Font Format)和WOFF2。
更详细的介绍字体的文章可以看:Web 字体简介: TTF, OTF, WOFF, EOT & SVG
简单来说,TTF和OTF都是没有压缩,所以这两个格式的字体文件会更大;EOT字体格式只有IE浏览器支持;SVG对文本的支持不够好…等等,因此使用WOFF可以说是前端开发的最佳选择了。(还有专家建议只用WOFF2字体,原文如下)
In fact, we think it is also time to proclaim: Use only WOFF2 and forget about everything else.
This will simplify your CSS and workflow massively and also prevents any accidental double or incorrect font downloads. WOFF2 is now supported everywhere. So, unless you need to support really ancient browsers, just use WOFF2. If you can't, consider not serving any web fonts to those older browsers at all. This will not be a problem if you have a robust fallback strategy in place. Visitors on older browsers will simply see your fallback fonts.
Bram Stein, from the 2022 Web Almanac
WOFF的优点主要有两个:
- 字体经过压缩,文件大小一般比TTF小40%;
- 主流的浏览器新版本都支持此格式;
WOFF2是WOFF的升级版,压缩率在此基础上提高了30%。
按需压缩字体
有时候前端页面中需要使用特殊字体的文本是固定的,那么就可以利用font-spider
(同类开源库还有fontmin
等)去将文本所对应的字体子集提取出来,生成一个新的子集包代替原本的全量字体包,这样即可大大减小字体文件,提高性能。
使用方式
font-spider 可以自动分析出页面中使用过的字体并进行按需压缩,也就是说,如果页面中的文本有改动就需要重新压缩。这种压缩方式比较适合页面的文字一般不会改动的场景。
下面是我对某个字体文件的提取对比,在这个情境中我的网站只有数字部分用到了这个字体,所以没必要把整个字体文件都打包进去,所以我使用了font-spider把字体文件中的数字部分提取出来了。
字体压缩前后的效果对比:
优化字体加载的方式
下图展示了浏览器绘制页面的过程:
从图片我们可以看出,浏览器从请求html文档到呈现文本,中间还有好几个步骤。因此字体的请求会比其他关键资源请求之后延迟很长时间,并且,在获取到字体资源前,浏览器处理文本的方式也各不相同。
在加载网页上的字体时,浏览器一般会有两种选择:
- 推迟渲染字体的时间直到字体资源下载完成(导致FOIT)。
- 在下载好字体资源前用备用字体展示该内容(导致FOUT)。
减少浏览器请求的次数
虽然在使用非默认系统字体时,用户一般不会在本地安装相应的文件,但是开发者仍然需要考虑到这种情况。在定义 font-face
时,可以使用 local()
先请求本地字体,示例代码如下:
@font-face {
font-family: 'Awesome Font';
font-style: normal;
font-weight: 400;
src: local('Awesome Font'),
url('/fonts/awesome.woff2') format('woff2'),
url('/fonts/awesome.woff') format('woff'),
url('/fonts/awesome.ttf') format('truetype'),
url('/fonts/awesome.eot') format('embedded-opentype');
}
对于这段代码,浏览器会按照下述顺序去请求资源:
- 浏览器渲染页面的DOM树,判断出需要加载哪个字体。
- 循环判断每个需要的字体是否可以本地使用。
- 如果本地没有该字体,就按照代码顺序去请求
url()
中定义的字体资源。- 如果有定义
format
,那么浏览器在下载字体资源前会先检查自己支不支持该格式,如果不支持,则继续请求下一行代码定义的字体资源。 - 如果没有定义
format
,则直接下载该资源。
- 如果有定义
使用 font-display
虽然不同的浏览器会对字体加载有不同的处理方式,但是开发者可以用 font-display
去控制这个行为。
font-display
在 CSS 层面上提供了此类问题的解决方法,它提供了五个属性:
-
auto:使用浏览器默认的行为;
-
block:浏览器首先使用隐形文字替代页面上的文字,并等待字体加载完成再显示;
-
swap:如果设定的字体还未可用,浏览器将首先使用备用字体显示,当设定的字体加载完成后替换备用字体;
-
fallback:与
swap
属性值行为上大致相同,但浏览器会给设定的字体设定加载的时间限制,一旦加载所需的时长大于这个限制,设定的字体将不会替换备用字体进行显示。 Webkit 和 Firefox 中设定此时间为 3s; -
optional:使用此属性值时,如果设定的字体没有在限制时间内加载完成,当前页面将会一直使用备用字体,并且设定字体继续在后台进行加载,以便下一次浏览时可以直接使用设定的字体。
采用什么样的加载策略取决于字体的重要性、个人审美以及性能考虑等等,因此在此不做推荐,以下是几种主要策略:
性能优先 | 速度优先 | 网络字体优先 | |
---|---|---|---|
推荐值 | optional | swap | block |
优点 | 非常高效,文本呈现时间短。 | 可以保证在使用网络字体的前提下尽早完成字体的渲染。 | 可以最大程度确保优先使用网络字体;尽管也会出现布局偏移,但是显示效果比swap的效果更平滑。 |
缺点 | 如果浏览器在限制时间内没有下载完字体,则不会加载网络字体。 | 如果字体请求过久,会导致页面出现FOUT闪烁。 | 这个策略推迟了文本的显示时间,导致FOIT闪烁。 |
综上所述,开发者可以根据实际情况选择不同的策略,例如font-display: swap
用于品牌和其他视觉上与众不同的页面元素;font-display: optional
用于正文中使用的字体。
自定义加载方式
如果开发者希望自定义字体加载的方式,并且愿意承担额外的js开销,那么还有以下选择:
- 利用 字体加载API 去定义或者操作字体的加载行为(但是在旧浏览器中不可用)
例如,如果您确定需要特定的字体变体,您可以定义它并告诉浏览器立即启动字体资源的获取:
var font = new FontFace("Awesome Font", "url(/fonts/awesome.woff2)", {
style: 'normal', unicodeRange: 'U+000-5FF', weight: '400'
});
// don't wait for the render tree, initiate an immediate fetch!
font.load().then(function() {
// apply the font (which may re-render text and cause a page reflow)
// after the font has finished downloading
document.fonts.add(font);
document.body.style.fontFamily = "Awesome Font, serif";
// OR... by default the content is hidden,
// and it's rendered after the font is available
var content = document.getElementById("content");
content.style.visibility = "visible";
// OR... apply your own render strategy here...
});
此外,因为您可以检查字体状态(通过
check()
)方法并跟踪其下载进度。
Ilya Grigorik, 《优化 WebFont 加载和呈现》
- 使用 Web Font Loader
只要在脚本中添加下列代码,即可使用它所提供的加载功能:
<script src="https://ajax.googleapis.com/ajax/libs/webfont/1.6.26/webfont.js"></script>
<script>
WebFont.load({
google: {
families: ['Droid Sans', 'Droid Serif']
}
});
</script>
它同时支持同步加载和异步加载,*度较高。
- 其他方式
由于自定义的可能性实在是太多了,业内也有好几个开源库,因此在这里不穷举所有的实现方式。有兴趣的朋友可以阅读A COMPREHENSIVE GUIDE TO FONT LOADING STRATEGIES,How We Load Web Fonts Progressively以及網頁加載字型Web Font FOIT& FOUT與效能測試等文章。
预加载字体资源
上文提到了如何控制浏览器处理暂不可用的字体,除此之外,开发者还可以通过 preconnect
和 preload
加快字体资源加载的速度。
preconnect
如果开发者选择在第三方站点上托管字体,那么可以用 preconnect
预先与第三方来源建立连接,示例:
<head>
<link rel="preconnect" href="https://fonts.com">
<link rel="preconnect" href="https://fonts.com" crossorigin>
</head>
preload
使用 preload
可以让浏览器优先下载字体资源,从而加快网页显示文本的速度,示例:
<head>
<!-- ... -->
<link rel="preload" href="/assets/Pacifico-Bold.woff2" as="font" type="font/woff2" crossorigin>
</head>
但是如果使用不当,预加载可能会对未使用的资源发出不必要的请求,从而导致性能损耗。
总结
最简单直接的优化方式就是只用系统字体(System Font)或者可变字体(Variable Font)来减少甚至清零所需要请求的网络字体的数量;如果不得不使用网络字体,可以试试上述方式去优化网页性能。这些优化手段可以互相组合去使用,取得1+1>2的效果。
本文只是一个粗略的介绍,对于细节问题没有过多涉及,欢迎在评论区补充更多信息。