字体包体积大源于未子集化,unicode-range仅控制渲染范围而非减小文件体积;需用pyftsubset提取真实用字并保留标点、ascii、数字及layout特性,构建时须基于子集文件重算hash。

中文字体包体积大,不是 CSS 写法的问题,而是你引入的字体文件本身没做子集化——@font-face 声明再规范,加载的仍是 10MB 的完整 SourceHanSansSC-Regular.otf,浏览器照卡不误。
为什么 unicode-range 不能替代字体子集化
很多人误以为在 @font-face 里写上 unicode-range: U+4F60-U+597D 就算“按需加载”了。其实这只是告诉浏览器:“这段字体负责渲染这些码位”,但实际 src 指向的文件仍是全量字体。浏览器不会自动裁剪字形数据,它只按规则匹配渲染逻辑。
- 实测:未子集的思源黑体 SC 正规版 TTF 文件大小为
12.3MB,加了unicode-range后 Network 面板里下载体积不变 -
unicode-range真正起作用的前提是——你已生成一个只含该码位的子集字体文件,并把它作为src引入 - 它对动态插入文本(如 i18n、API 返回标题)无感知,无法规避漏字风险
用 pyftsubset 提取真实用字最稳
pyftsubset(FontTools 子命令)目前是对 OpenType 特性支持最完整的子集工具,尤其适配 Noto、Source Han 等现代中文字体。关键不在命令怎么敲,而在“字集”从哪来。
- 别只扫 HTML 文本:要合并
document.body.innerText+ 所有data-*属性值 +alt+placeholder+ JS 动态文案(比如i18n.t('home.title')对应的翻译字符串) - 必须保留非汉字字符:标点(
,。!?;:""''()【】)、ASCII 字母、数字、空格,否则按钮上 “Submit” 或错误页 “404” 会变方块 - 加
--layout-features="*":否则「一」字在不同上下文可能调用不同连笔/竖排字形,子集后统一显示成默认形,视觉断裂 - 示例命令:
pyftsubset SourceHanSansSC-Regular.otf --text-file=chars.txt --output-file=font-subset.woff2 --flavor=woff2 --layout-features="*"
构建阶段必须重算文件 hash
Webpack 或 Vite 下,如果字体文件 hash 还是基于原始 TTF 计算的,那哪怕你替换了子集后的 .woff2,浏览器缓存也不会更新——用户永远加载旧的全量文件。
- Webpack 场景:用
file-loader或asset模式时,确保filename中的 hash 来自子集后文件内容(例如通过自定义 loader 读取font-subset.woff2的 buffer 再哈希) - Vite 场景:避免直接
import原始 .otf;改用插件(如vite-plugin-fonts)或预构建脚本,在build.rollupOptions.ongenerate阶段注入子集结果 - CI 中加校验步骤:对比子集前后 glyph 总数(
fonttools ttx -o - font-subset.woff2 | grep "GlyphID" | wc -l),防止空子集或漏字上线
真正难的不是跑通一条命令,而是把 JS 动态文案、多语言 key、表单 placeholder 这些“看不见的文本”全部纳入字集提取链路——漏掉任意一处,上线后某个按钮就突然变豆腐块,还没法复现。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











