中文字体 18.6 MB,我只给网页发了 232 KB

经验 2026-10-23 20:05 · 42 次阅读 ·字体,网页性能,实测

同一份中文字体,按页面用到的字裁剪后的大小

我本来只想给网页换个中文字体,结果发现字体包 18.6 MB。按页面实际用到的字裁一遍,发出去的是 232.7 KB,是原来的 1.22%。

背景

站上的正文一直用系统默认字体。我想换成思源黑体(Noto Sans CJK),服务器上这个字体放在 /usr/share/fonts/opentype/noto/,文件 19,484,784 字节,也就是 18.6 MB。

这一个文件里装了 10 套字体:简体、繁体、日文、韩文,每套还带一个等宽版本。我只要简体那套。

现在文章页整页 10,138 字节,压缩后传输 4,694 字节。挂上原字体,访客看一篇文章要先下 18.6 MB。就像为了查一个字,买下整本字典。

做法

先把简体那一套从大包里拆出来。这种「一个文件装多套字体」的格式叫 ttc(字体合集)。用 Python 的 fontTools 拆:

from fontTools.ttLib import TTCollection
c = TTCollection('/usr/share/fonts/opentype/noto/NotoSansCJK-Regular.ttc')
print([f['name'].getDebugName(4) for f in c.fonts])  # 先看 10 套都叫什么
c.fonts[2].save('NotoSansSC-full.ttf')               # 第 3 套才是简体

拿到单套简体:16,437,340 字节(15.7 MB),44,810 个字符,拆包花了 0.85 秒。还是太大。

再数页面到底用到多少字。我直接查站点数据库里的 17 篇文章正文:

cur.execute('SELECT body FROM articles WHERE published=1')
txt = ''.join(r[0] for r in cur)
print(len(txt), len(set(txt)))     # 35656 1024

35,656 个字,不重复的只有 1,024 个(汉字 894 个)。整本 44,810 个字,我站上只用得到这一千个。

按这份字表裁剪。这个动作叫子集化(只留下用到的字,其余扔掉)。工具是 fontTools 自带的小程序,一条命令,跑 3.5 秒:

python3 -m fontTools.subset NotoSansSC-full.ttf \
  --text-file=site-chars.txt \
  --output-file=site-sub.woff2 \
  --flavor=woff2 --no-hinting

--flavor=woff2 指输出成 woff2,这是网页专用的字体格式,浏览器直接认。--no-hinting 是去掉屏幕上微调字形的小指令,网页上基本看不出来。

同一次裁剪,换个存法体积差很多:

存法 大小 说明
原始 ttc 18.6 MB 10 套字体,不能直接给网页
单套简体 ttf 15.7 MB 44,810 个字符
整包转 woff2 10.9 MB 压掉 41%,还是 10 MB 级
子集 ttf 318.4 KB 只留 1,022 个字
子集 ttf 再 gzip 264.1 KB 比原样小 17%
子集 woff2 232.7 KB 整包的 1.22%
子集 woff2 关排版特性 141.0 KB 再省 39.4%

「整包转 woff2」是把整个 15.7 MB 的 ttf 直接存成 woff2,花了 71.8 秒,出来 10.9 MB。光靠压缩救不了,得裁字。

末行的 141.0 KB 是加了 --layout-features='',把连字、标点挤压这些排版特性一起删掉。横排正文看不出区别,竖排和精细标点就丢了,页面用不上再删。

单篇文章单独裁更小:最新那篇正文 2,549 字、282 个不重复汉字,裁出来 76.9 KB,是整站子集的三分之一。但文章一多就得一篇一个文件,浏览器缓存反而用不划算,我这次还是共用一份。

文件变小不等于浏览器用上了。我用浏览器调试接口(CDP)把字体挂到测试页,量了三组:

  • 拉丁文 Video CRF 23:子集字体宽 240.53 像素,系统衬线字体 230.97 像素,差 4.14%,字确实换上了。
  • 常用汉字那段,两种字体渲染出来的像素对不上,也是换上了。
  • 「囧犇鑫」三个字,两种字体渲染逐像素完全一致,差异区返回空。子集里没有这三个字,浏览器悄悄退回系统字体。

再看慢网。把网速限到 125 KB/s(大约 1 Mbps),在 0.6 秒、1.2 秒、2.0 秒各截一次图,数深色像素:

  • 没写 font-display 的页面:0.6 秒和 1.2 秒都是 0 个深色像素,整段字不见,2.0 秒才出来。
  • 写了 font-display: swap 的页面:0.6 秒就有 14,631 个深色像素(先用系统字体顶上),2.0 秒字体到货变成 14,650,字换了、位置没跳。

结论与踩坑

232.7 KB 是整包的 1.22%,比一张手机照片还小。裁一次 3.5 秒,这步别省。

四个坑:

  1. 缺字不报错。子集里没有「财」字,页面照样渲染,只是那个字换成系统字体,粗细跟别处不一样。加了新文章要把新正文一起喂进 --text-file 重裁。
  2. 不写 font-display 就是白屏。默认行为是等字体到齐再显示文字,1 Mbps 下实测空白超过 1.2 秒。写 font-display: swap。
  3. 别直接挂 ttc。浏览器认它,实测加载成功,代价是把 19,484,784 字节全下下来。
  4. woff2 比 gzip 过的 ttf 还小 12.6%(232.7 对 264.1 KB)。服务端开压缩替代不了它。

下次复用按这个顺序:数用字 → 裁子集 → 存 woff2 → 限速看一眼。

继续看

全部 →