自托管字体的一年
中文网站做自托管字体,最大的障碍不是版权,是体积。
一套完整的霞鹜文楷,woff2 格式大约 20-25 MB。用户打开首页就要下载这个?显然不行。
所以大部分中文站的选择是:用系统字体。然后全站看起来就像一张没洗过的 PPT。
分片是怎么解决的
关键洞察:一篇文章不可能用到所有汉字。
常用的 3500 个汉字覆盖了 99.5% 的日常文本。剩下几万个字可能一辈子都不会出现在我的博客里。
所以现代中文字体包的做法是:
- 按 Unicode 范围把字体切成几百个小文件
- 每个文件用
unicode-range声明自己负责哪些码位 - 浏览器只下载页面实际用到的那些分片
/* 分片 #12 负责这些码位 */
@font-face {
font-family: 'LXGW WenKai Lite';
font-weight: 400;
font-display: swap;
src: url('./files/subset-12.woff2') format('woff2');
unicode-range: U+4E00-4E1F, U+4E30-4E4F;
}
浏览器渲染一段文字时,会先检查每个字符属于哪个 unicode-range,只下载覆盖到的分片。
实际数字
我这个博客用 lxgw-wenkai-lite-webfont:
| 项目 | 数值 |
|---|---|
| 包总大小 | 25.1 MB |
| 分片数量 | 582 个 |
| 单篇长文实际加载 | 3-5 个分片 |
| 实际传输 | 约 180 KB |
| 首屏延迟 | 0(font-display: swap) |
25MB 的包,实际只用了 180KB。
font-display: swap 的必要性
@font-face {
font-display: swap;
}
swap 的意思是:先用后备字体渲染,字体加载完再换。
代价是会看到一次字体闪烁(FOIT → FOUT)。收益是文字永远可读。
另外两个选项都不适合中文:
block:字体没加载完就不显示文字。中文 180KB 可能要 500ms,用户会看到一片空白optional:网络慢就永远不用自定义字体。会导致同一篇文章有时是文楷有时是黑体
swap 是唯一合理的选项。
后备字体链要写对
font-family:
'Inter Variable',
'LXGW WenKai Lite',
'PingFang SC',
'Microsoft YaHei',
sans-serif;
这个链的顺序有讲究:
- Inter 负责拉丁字母和数字 —— 它的西文比文楷好看得多
- 文楷 负责汉字 —— 在 Inter 没有对应字形时接管
- 苹方 / 微软雅黑 —— 文楷分片还没加载完时的后备
- sans-serif —— 最后兜底
关键在于第 1 和第 2 的顺序:浏览器是逐字符查找的。英文字符在 Inter 里能找到,就不会去问文楷;汉字在 Inter 里找不到,自然落到文楷。
这样西文和中文各用各的最优字体,不需要任何 unicode-range 技巧。
等宽字体只给数据
font-family: 'JetBrains Mono', 'LXGW WenKai Lite', ui-monospace, monospace;
等宽字体我用在四个地方,只有四个:
- 日期(
2025-10-28) - 阅读时长(
6 min) - 代码块
kbd元素
不用它做装饰性的小标签。 那些 MONOSPACE 风格的 eyebrow 文本是模板化的标志之一——它看起来"有技术感",但没传达任何信息。
一个坑
字体分片依赖 unicode-range,而 unicode-range 是静态声明的。
意思是:如果你的文章里出现了某个生僻字,而那个字所在的分片没有被任何 @font-face 覆盖(有些字体包会漏掉扩展区),那个字就会掉到后备字体。
结果是一篇文章里 99% 是文楷,中间蹦出一个黑体的字。
我的处理是接受它——比为了一个字加载一整个 3MB 的扩展区划算得多。
结论
中文字体不是"能不能自托管"的问题,是"有没有做分片"的问题。
用了分片之后:
- 首屏字体成本 ≈ 180KB(可接受)
- 视觉一致性 100%(全站统一字体)
- 零第三方请求(隐私友好,也不受 CDN 挂掉影响)
这套方案我已经跑了一年,没有换回去的理由。
评论暂未开放