【优化篇】网站中文字体包优化指南

前言

在开发中文网站时,我们通常需要下载汉字字体包,但众所周知,汉字字体包的体积通常比较大,以谷歌字体中的Noto Sans Simplified Chinese字体为例,直接下载下来的完整字体文件体积为17.7MB。

17.7MB的大小对于一个页面资源来说是巨大的;尽管现代的网络环境中,许多浏览器和服务器已经开始广泛支持 HTTP/2 协议或者更高版本的协议,单个网络资源的请求并不影响其他资源的传输,但其可能会占用大量带宽其他资源的请求可能会因为带宽限制而排队等待传输。这种情况尤其在网络条件较差或者连接较慢的情况下更为明显。

本文将由浅入深,讲述font-display、woff2、font-spider、字体分片4种字体包优化方式。

优化手段

font-display

浏览器在加载字体文件时会有三个时期:

  • 阻塞期(Block Period)。在此期间如果字体没有加载完成,那么浏览器会使用 font-family 指定的字体列表中的后备字体(Fallback)进行渲染,但是显示为空白,也就是对于用户是不可见的。在此期间字体加载完成之后才能正常显示该字体。

  • 交换期(Swap Period)。跟阻塞期类似,但是在这个时期内,它会在字体加载时,先用后备字体渲染文本并显示出来(而不是显示空白),在此期间字体加载完成之后才能正常的显示该字体。

  • 失败期(Failure Period)。如果字体加载失败,则使用后备字体显示文本。

也就是说对于一个字体的优化,我们要尽可能缩短字体加载的阻塞期,css指令font-display便可以帮助我们操作以上生命周期;

其属性值如下:

  • auto:字体显示策略由用户代理定义。

  • block:为字体提供一个短暂的阻塞周期和无限的交换周期。

  • swap:为字体提供一个非常小的阻塞周期和无限的交换周期。

  • fallback:为字体提供一个非常小的阻塞周期和短暂的交换周期。

  • optional:为字体提供一个非常小的阻塞周期,并且没有交换周期。

那么在了解 font-display 之后,那么我们应该不难看出来,对于大部分情况应该把它的值设置为 swap ,这样在加载网络字体期间,使用后备字体进行渲染,加载完成之后在替换为指定的网络字体。

css 复制代码
@font-face {
  font-family: ovo;
  src: url("xxx.woff2") format("woff2");
  font-weight: normal;
  font-style: normal;
  font-display: swap;
}

swap虽然解决了字体包阻塞期问题,但它却为我们带来了另一个问题:页面闪烁,如果页面依赖于外部字体文件来渲染文本内容,当字体文件加载较慢时,可能会导致页面上的文本内容在字体加载完成前先以默认字体显示,之后再切换到所需字体。这种闪烁可能会给用户带来不稳定的加载体验。

woff2

这里直接说结论,在常见的几种字体文件类型中,如OTF、TTF、WOFF、WOFF2等,WOFF2在体积的表现最好,其兼容性在现代的互联网环境下也不再是问题。 这里对于其他字体文件类型不多做赘述,有兴趣可以查看掘友的这篇文章:不同的字体文件类型(OTF、TTF、WOFF、WOFF2)

font-spider

对于静态网站而言,一个页面中会出现哪些字体是固定的,那么我们可以将字体包中没有用到的字体筛除,font-spider便能够帮我们处理这个场景。

其大致原理是去静态解析构建后的web包,找到自定义的字体所用的CSS选择器,下一步匹配并提取出使用这些选择器的html元素下的字符,再经由fontmin压缩出一套新的字体包替代原来的全量字体包。

使用流程也非常简单,执行命令即可:

shell 复制代码
npm i font-spider -g
font-spider index.html

但是对于SPA的场景或者后端返回不同文字的场景,font-spider并不难全部爬取到,所以该工具对于一些动态内容的网站覆盖不够全面。

字体分片【重点】

字体分片是工作中最实用的字体优化方案,它依靠CSS字体指令中的unicode-range将一个大型字体文件分割成了上百个小号文件,浏览器只会在页面使用了该范围中的字符时才会加载对应的字体"分片"。也就是说页面初始化时给浏览器一个目录,在页面要渲染字体时再去根据目录加载所需的字体文件。

这其实就相当于字体的"运行时按需加载",不仅用起来省心,把 css 引入只来之后就不需要管了,对字体的加载速度也有了显著的提升。

打开google提供的字体css在线链接,可以看到其按照一定的粒度,将字体分成多个文件,再根据css中unicode-range来给不同文字加载不同的字体包资源。

该方案网上已经有许多现成的案例,比如:cn-font-splitfont-slice...

其使用方法也相当简单,照着官网很快能完成,其效果以我的个人网站为例,原本一个4.2MB的中文字体包,经过分片以后,按需请求了若干个大小30kb左右的字体。

最后,让我们通过performance对笔者的个人网站进行分析,可以观察到,当不对字体文件切片时,字体文件的请求竟然高达9.5s!而在切片以后,浏览器会对多个chunk同时请求,运用了http2多路复用的特点,实现字体的快速加载。

参考文献

相关推荐
kyriewen10 分钟前
折腾了半年 AI 编程工作流,最后发现效率瓶颈是桌上那块屏幕
前端·javascript·ai编程
蜗牛前端37 分钟前
codex 全流程开发上线的高颜值礼簿小程序
前端·微信小程序
大龄秃头程序员1 小时前
我在图文流 App 里落地双层缓存、弱网降级与 OOM 治理
前端
老王以为1 小时前
React Renderer 分离的多平台架构
前端·react native·react.js
hunterandroid1 小时前
Kotlin Coroutines 与 Flow:让异步任务更清晰
前端
Bigger2 小时前
从零搭建 AI 代码审查服务:一份前端也能看懂的 Python 学习笔记
前端·ci/cd·ai编程
lichenyang4532 小时前
JSAPI、NAPI、Biz、Imp:ASCF Demo 如何真正调用系统能力和 C++ 能力
前端
自由路飞3 小时前
RAG 混合检索深挖:BM25 和向量分数为什么不能直接相加?
面试
lichenyang4533 小时前
IPC、JSVM、UIThread、libuv:ASCF 架构图里最容易混的几个词
前端
用户059540174463 小时前
Redis记忆存储故障恢复测试踩坑实录:手动测试让我漏掉了2个一致性Bug
前端·css