中文字体的优雅解决方案——对字体进行切片

2,996 阅读4分钟

原文地址:中文字体的终极解决方案——对字体进行切片

Demo

引言

中文字体在使用过程中有着不便之处,因为中文字体较多,中文字体包也一般偏大,3、4MB大小很正常,直接引入字体包会减慢用户的加载速度,并造成不必要的资源浪费。因此,对中文字体进行压缩是很有必要的事情。

方案

一种解决方案是提取中文字体的子集。

主要原理是扫描你的代码及博文中所用到的汉字,然后提取字体文件的子集,从而达到一个比较小的字体加载体积。我在之前一篇文章中写过这部分的处理,感兴趣可以前往查看——对中文字体进行压缩

但是这种方法在面对用户自定义输入,比如评论以及后端返回内容时不能很好地处理。

那 Google Fonts 是怎么处理中文字体的呢?

image 20210619122937737 image 20210621224958862

design.google/news/google…

它采用了机器学习等手段,将字体拆分成合适的粒度,比如把一个 4MB 的字体包分成 100 个 40KB 的字体包,这样的话,一般网页中使用到的中文也只是一部分字体,只需要加载多个资源包就能完全覆盖。同时,就算网页中有很多生僻字,需要付出的代价也只是多加载几个资源包。

它的 css 引入文件示例如下:

@font-face {
  font-family: 'Noto Sans SC';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url(https://fonts.gstatic.com/s/notosanssc/v12/k3kXo84MPvpLmixcA63oeALhLOCT-xWNm8Hqd37g1OkDRZe7lR4sg1IzSy-MNbE9VH8V.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;
}
/* [5] */
@font-face {
  font-family: 'Noto Sans SC';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url(https://fonts.gstatic.com/s/notosanssc/v12/k3kXo84MPvpLmixcA63oeALhLOCT-xWNm8Hqd37g1OkDRZe7lR4sg1IzSy-MNbE9VH8V.5.woff2) format('woff2');
  unicode-range: U+fee3, U+fef3, U+ff03-ff04, U+ff07, U+ff0a, U+ff17-ff19, U+ff1c-ff1d, U+ff20-ff3a, U+ff3c, U+ff3e-ff5b, U+ff5d, U+ff61-ff65, U+ff67-ff6a, U+ff6c, U+ff6f-ff78, U+ff7a-ff7d, U+ff80-ff84, U+ff86, U+ff89-ff8e, U+ff92, U+ff97-ff9b, U+ff9d-ff9f, U+ffe0-ffe4, U+ffe6, U+ffe9, U+ffeb, U+ffed, U+fffc, U+1f004, U+1f170-1f171, U+1f192-1f195, U+1f198-1f19a, U+1f1e6-1f1e8;
}
...

通过 unicode-range 来描述每个字体包中包含的字体子集,这也是它能够根据页面上使用的字体来智能加载对应的字体包的原理。

Google Fonts 实现了一套通过 unicode-range 来分割字体的方法,那么这个分割字体的具体方案,是不是能对所有的中文字体都生效呢?

我是这么想的,也这么尝试了下,所做的内容如下:

  1. 提取 google fonts 的 unicode-range。
  2. 提取要处理的字体包含的所有字符,得到 google fonts 的 unicode-range 和字体里包含的字符的交集部分。
  3. 将字符按照上面步骤得出的拆分方案,提取字体子集,生成多个文件及 css 样式文件。

最终展现出的效果还不错。

Demo 在本文开头,或者前往这里——对中文字体进行切片

数据

得意黑字体为例为例:

处理前 ttf 大小 2074KB,woff2 大小 928KB。

处理后每个类型的字体生成 95 个文件:
ttf   总大小为 2.3M (最小文件 3.4K,最大文件 55K)
woff2 总大小为 1.3M (最小文件 1.5K,最大文件 33K)

实际加载页面的体积由页面使用的字符决定,一般情况下,只需要加载少于 20% 的内容即可完整展示一个页面。

以该页面为例,只需要加载 386KB 就能覆盖全部字符。

使用

github 地址: github.com/voderl/font…

  1. 安装
npm install --save-dev font-slice
yarn add -D font-slice
  1. 使用
const createFontSlice = require('font-slice');

createFontSlice({
  // fontPath
  fontPath: path.resolve(__dirname, 'YourPath.ttf'),
  // outputDir
  outputDir: path.resolve(__dirname, './output'),
})

可能等待时间较长,请耐心等待,完成后可以直接预览字体。

  1. 引用生成的 font.css 文件,设置对应的 fontFamily 即可

将生成的产物部署到 cdn 上,直接引用 cdn 的地址就可以了。

更多配置项请前往 github 页查看。

如果遇到问题可以联系我或者提 issue 处理~

感谢