前情提要
把博客搭建起来后,不修改一下其外观就心痒痒,于是便对主题下手了…修改了博客的字体,同时也让在主页的博文能够显示摘要。
花了最多时间的应该是字体方面。一开始,直接在样式表里指定渲染博文时应当加载完整的字体文件 (约 17 MB),而完整字体文件又相当大,导致浏览器在加载字体这项工作上花了相当长时间,而这段时间里用户是看不到一点博文的…于是乎,我决定压缩字体。
直接压缩整个字体文件
最简单的当然是直接压缩整个字体文件。在这里我采用了 Python 库 fonttools。这个库还有 cli,直接运行 fonttools ttLib.woff2 compress <字体文件>
即可从 otf 或 ttf 文件生成 woff 文件,压缩率较高,此时字体文件来到 10 MB 的量级。虽然压缩成果显著,但是还是大,需要花非常多的时间加载。
裁切
裁切也是一个良好的策略,让浏览器只加载必要的字体。一开始,我使用命令行统计了会在博客中出现的所有字符,并利用先前提到的工具 (使用子模块 subset
) 生成字体子集并再压缩。效果非常显著,字体文件来到了 150 KB 的级别,迈出了史诗级的一步。但是,这么做的话,如果新的博文里有以前从未出现过的字符,那么我们就需要重新生成整个字体文件,或者先让这个“残缺版”的字体文件被加载,等到完整版字体文件被下载并加载完成后,再替换成完整字体文件。我也做了后面这个方案,这需要用到 CSS 中的 font-display
属性来实现 (见 font-display | MDN)。该属性有多个取值,当其为默认值或者是 block
时,浏览器不将完整字体文件加载出来就不会显示字符。但是当它为 swap
时,浏览器会在它还没加载出来时先显示备用字体,所以我们可以设置完整版字体的该属性值为 swap
,裁切版则随意,然后再设置任意一个最保底的字体。本博客使用的保底字体是一类字体,名为 serif
。
分片
裁切固然是个方案,但是字体还是略大。为了减小字体文件,我们可以将字体文件分片。利用 unicode-range
属性指示该字体文件所含的字符,方便浏览器判断 (见 unicode-range | MDN)。所以后来我又写了个脚本将裁切的字体分成几个文件,每个只含约 200 字。这就需要自己写 Python 代码来实现了,而且并不是最好的,因为还是要在博文更新时也更新一下字体,而且总要保留完整字体文件,所以后来我把这个方案相关的文件都删除了,现在只能在 Git 的提交记录中看到它们了。
我发现的最佳解决方案是——基于 Google 统计的字符出现频率,对字体进行分片。这里有现成的项目,帮助我们将一个 ttf 文件分成多个 woff2 文件再生成一个配置好 unicode-range
的 css 文件,而且使用科学的分片方案。字符囊括全面,也不需要特意保留完整字体文件 (想的话还是可以保留)。
我使用该项目帮助我生成了所有的分片文件。唯一美中不足的是它只支持 ttf 格式,而且生成的字体比以前的纤细一些,所以我将博客字体从 Regular 字重转换到 Medium 字重。
一个完整的字体文件被拆分成了约 100 个小字体文件,每个约 30 KB,减少了加载量,提升了加载速度 (而且允许并行加载)。这样也不会让博客加载半天显示不出东西,加载出的字体会迅速替换掉后备字体。
缓存
实际上,字体在网站的一生里几乎不会变化,我们大可将其允许缓存的时间设置得相当长,以便浏览器下次使用,让用户在下次打开网页时加载速度更加快。这里可见 Cache-Control | MDN (max-age
段)。
总结
为了让博客的加载速度提升,确实是让人煞费苦心。字体是一个网站里相当大的文件,需要特别优化。
其他
博客字体:
- Shanggu - 基於思源的傳承字形(舊字形)字體
- Cascadia Mono (在本博客未分片)
- Source Serif (在本博客未分片)
如果你很好奇我对主题的其他修改,可以向我发送电子邮件讨论。
后记
似乎没有必要引入 Source Serif,因为本博客的主要字体 Shanggu Serif 就是基于 Source Han Serif CJK 制作的,其西文部分与 Source Serif 几乎没有区别。不过在我这,我发现 Shanggu Serif 的西文字形比 Source Serif 的略大。罢了,既然引入了就不改了,就这样用吧。