沉寂的博客

沉寂的博客

字体分包,提高网页字体载入速度

205
2024-06-28
字体分包,提高网页字体载入速度

起因

想给博客更换一个字体, 将.ttf格式的字体文件转换成.woff2格式的字体文件并上传到云存储上,写好要注入的代码,一切准备就绪后,发现字体加载并不理想。虽然已经上传到云存储上,然而加载时还是出现了明显的等待加载的情况。

注入的代码如下:

<style>
  @font-face {
    font-family: "LXGW ZhiSong MN";
    src: url("https://example.com/font/LXGWZhiSongMN.woff2") format("woff2");
  }
  body,#post-content,.markdown-body {
    font-family: "LXGW ZhiSong MN",PingFang SC,Hiragino Sans GB,Droid Sans Fallback,Microsoft YaHei,sans-serif !important;
  }
</style>

如下图所示,将网速限制到500KB/s模拟网络一般的情况,一份3MB的字体文件,需要6秒的加载时间,这也就导致了内容使用其他字体显示完毕后,过了6秒字体突然发生了变化,这样的效果就不太理想了。

探索

观察到大佬https://ryanc.cc/的博客字体加载就非常丝滑,仔细看了一下注入的代码,发现是引入了一个css文件,文件包含大量的font-face定义,如下所示:

/* LXGW WenKai Mono [4] */
@font-face {
  font-family: 'LXGW WenKai Mono';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url('./files/lxgwwenkaimono-regular-subset-4.woff2') format('woff2');
  unicode-range: U+1f1e9-1f1f5, U+1f1f7-1f1ff, U+1f21a, U+1f232, U+1f234-1f237, U+1f250-1f251, U+1f300, U+1f302-1f308, U+1f30a-1f311, U+1f315, U+1f319-1f320, U+1f324, U+1f327, U+1f32a, U+1f32c-1f32d, U+1f330-1f357, U+1f359-1f37e
}
/* LXGW WenKai Mono [5] */
@font-face {
  font-family: 'LXGW WenKai Mono';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url('./files/lxgwwenkaimono-regular-subset-5.woff2') format('woff2');
  unicode-range: ······
}
······

观察以上代码可以发现,一个字体文件,被拆分为若干个字体文件,并且通过unicode-range来定义了这个哪些字符使用这个字体文件。 通过Chrome控制台的Network选项卡也能看到大量字体文件的分片被加载进来,在低网速下可以看到字符一片一片的变为新的字体。

这种方法还有一个优势在于:当一个字体文件被拆分为若干个字体分片后,由于unicode-range定义了每个分片所对应的字符,那些未被使用到的分片就不会被加载。这样不仅提高了加载效率,也能节约网络资源,节约CDN流量,带来的浏览体验也会更好。

实践

知道了所使用的技术,不如马上来实践吧。

通过搜索,发现了中文网字计划的字体分包项目,该项目同时还提供了在线分包功能,通过浏览器可以快速进行字体的分包。不过在线分包功能是有限制的,例如无法自己指定分包文件名,而是使用默认的哈希文件名,但这除了文件名可读性变差以外,在浏览器加载上没有太大的影响。

接下来介绍在线分包与Node.js分包的步骤。

在线分包

首先进入在线分包网站,在网页左侧选择要分包的字体文件,格式方面支持otf、ttf,若有已经转换的woff2格式,也可直接选用。

选择字体文件后,点击左侧的开始分包按钮启动分包任务,只需等待数秒分包任务即可完成。分包完成后,点击右下角的“压缩下载ZIP”按钮即可打包下载分包后的字体文件。

下载后可以看到压缩文件中除了分包后的woff2字体文件、一个result.css样式表文件,还有index.html、preview.svg、reporter.json三个文件,这三个文件在实际使用中不需要用到,删除后上传剩余文件到网站后台或者云存储服务上即可。使用时只需引入result.css就可以调用对应的字体。

注意:在线分包的方式不支持一些自定义项的设置,如果想要自定义输出的分包字体文件,请看接下来的Node.js分包。

Node.js分包

首先需要电脑中已经安装Node.js,且版本大于18。关于Node.js的安装,在此不再赘述。

输入以下命令,进行全局安装

npm install cn-font-split -g

安装后,使用以下命令执行字体文件的分包。其中,-i指定的是字体文件的路径,-o指定的是输出目录。

cn-font-split -i=./input.ttf -o=./temp

分包的速度相较浏览器分包而言,速度有较大的提升。打开指定的输出目录,能看到与浏览器分包一样的文件,将分包后的字体文件及css样式表文件上传即可,与在线分包一致。

这种方式分包所输出的文件名为哈希值,如需自定义输出文件名,可使用以下命令(Windows命令提示符环境可能需要使用双引号):

cn-font-split -i=./input.ttf -o=./temp --renameOutputFont='ExampleFontName.[index][ext]'

使用以上命令,输出的分包文件将会以ExampleFontName.0.woff2这样的格式命名,此处的ExampleFontName是自定义的字符串,[index]占位符为自增索引,[ext]占位符为文件扩展名。

如需更多自定义的设置,可以输入cn-font-split -h 查看参数说明。更多有关信息,请访问该项目的页面。

https://github.com/KonghaYao/cn-font-split

结束

对字体文件进行了分包,当然要测试下分包后的加载效率。

同样将网速限制到500KB/s,在不使用缓存的情况下加载网页。可以看到,由于定义了css的unicode-range参数, 一共66个字体分包,只加载了12个,总大小700KB,1.5秒就加载完成了,仅是之前6秒的四分之一,加载效率得到了很大的提升,同时也避免了字体长时间等待加载出现的“画风(字体)突变”的情况。