在开发 OneKit 的字体工具时,我发现了一个有趣的现象:很多用户上传的字体文件动辄十几兆,但实际在网页上只用了标题里的几个字。这种"杀鸡用牛刀"的做法不仅浪费带宽,还严重拖慢了网页加载速度。其实,通过字体子集化技术,我们可以轻松地把字体体积缩小 90% 以上。
中文字体动不动就几兆甚至十几兆,简直是网页加载速度的杀手。每次看到设计师甩过来一个 20MB 的字体文件还要我保证秒开,我都想顺着网线过去... 只能苦哈哈地做子集化。
中文字体的体积问题
在网页开发中,自定义字体能够极大地提升页面的视觉表现力。然而,对于中文网站来说,使用自定义字体面临一个严峻的问题:字体文件体积过于庞大。一套完整的中文字体文件通常在 5MB 到 20MB 之间,部分包含繁体字和生僻字的字体甚至可以超过 30MB。这与英文字体通常只有几十 KB 到几百 KB 的体积形成了鲜明的对比。
造成这种体积差异的根本原因在于字符数量。英文字体只需包含 26 个字母的大小写形式、数字和标点符号,总共不过百余个字形。而中文字体需要覆盖的字符集远大于此。GB2312 标准收录了 6763 个汉字,GBK 扩展到了 21886 个汉字,而完整的 Unicode CJK 统一表意文字区域包含超过 97000 个字符。每一个字符都需要存储精确的轮廓数据,这直接导致了文件体积的膨胀。
在实际的 Web 性能优化中,这样的大体积字体文件会带来多方面的负面影响。首先是加载时间显著增加,在普通 4G 网络环境下,加载一个 10MB 的字体文件可能需要 5 到 8 秒,这对用户体验是致命的打击。其次是带宽成本上升,尤其对于流量敏感的移动端用户而言,一次页面访问就消耗数十兆字节的流量是不可接受的。最后,过大的字体文件还会导致 FOIT(Flash of Invisible Text)或 FOUT(Flash of Unstyled Text)现象更加明显,在字体加载完成之前,页面文字要么完全不可见,要么使用回退字体显示,严重影响页面渲染的稳定性。
什么是字体子集化
字体子集化(Font Subsetting)是解决中文字体体积问题的核心技术方案。它的基本原理非常直观:既然一个网页不可能用到字体文件中的所有字符,那么我们只需要保留页面实际使用到的那些字符,去掉其余全部不需要的字形数据。这样处理后的字体文件就是原始字体的一个「子集」。
具体来说,子集化工具会分析字体文件内部的字形表(glyph table),根据你提供的字符列表,仅提取对应的轮廓数据、度量信息和排版提示(hinting)信息,重新生成一个精简的字体文件。由于丢弃了大量未使用的字形数据,子集化后的字体体积可以缩减 95% 以上。
以一个常见的企业官网首页为例,页面上实际出现的不重复汉字通常在 200 到 500 个之间。即使加上标点符号、英文字母和数字,总字符数也很少超过 600 个。从包含数万字符的完整字体中仅提取这几百个字符,文件体积的缩减效果是非常惊人的。原本 12MB 的字体文件,经过子集化后可以压缩到 50KB 至 150KB,完全可以在毫秒级时间内完成加载。
子集化的具体方法
实现字体子集化主要有三种方法,各有适用场景。
方法一:手动指定字符列表
最直接的方式是手动收集页面中所有出现的文字,整理成一个不重复的字符列表,然后交给子集化工具处理。这种方法适合内容固定、不经常变动的页面,例如品牌官网的首屏标语、产品名称标题等。手动方式的优点是精确可控,缺点是页面内容更新后需要重新生成子集。
方法二:自动化页面扫描
对于内容较多或经常更新的网站,可以编写脚本自动扫描页面中使用的所有文字。脚本会遍历 HTML 文档中的文本节点,提取全部不重复的字符,然后自动调用子集化工具生成精简字体。这种方法可以集成到构建流程中,实现完全自动化。常用的工具包括 Python 的 fonttools 库和 Node.js 的 subset-font 包。
方法三:使用预定义常用字符子集
如果无法精确预知页面内容(例如用户生成内容的场景),可以使用预定义的常用汉字子集。统计数据表明,最常用的 3500 个汉字可以覆盖日常阅读 99.48% 的内容。使用这一子集,字体体积通常可以控制在 300KB 到 500KB 之间,在通用性和体积之间取得了较好的平衡。另一种方案是按 Unicode 范围分片加载,利用 CSS 的 unicode-range 属性实现按需加载,只有当页面中出现该范围内的字符时浏览器才会下载对应的字体分片。
字体格式选择
子集化之后,选择合适的字体格式可以进一步压缩文件体积。目前 Web 上常见的字体格式有 TTF、OTF、WOFF 和 WOFF2 四种,它们在压缩率和浏览器兼容性方面各有差异。
| 格式 | 压缩方式 | 体积对比 | 浏览器支持 |
|---|---|---|---|
| TTF / OTF | 无压缩 | 基准 (100%) | 所有浏览器 |
| WOFF | zlib 压缩 | 约为 TTF 的 60%-70% | IE9+ 及所有现代浏览器 |
| WOFF2 | Brotli 压缩 | 约为 TTF 的 30%-40% | Chrome 36+, Firefox 39+, Safari 12+ |
字体格式体积对比
同一字体在不同格式下的体积(以思源黑体常用子集为例)
从上表可以清楚地看到,WOFF2 在压缩率方面具有明显优势,相比原始 TTF 格式可以再压缩 60% 到 70%。结合子集化操作,先提取所需字符再转换为 WOFF2 格式,可以实现最大程度的体积优化。考虑到 WOFF2 目前在全球范围内已经拥有超过 97% 的浏览器支持率,推荐将 WOFF2 作为首选格式,同时提供 WOFF 作为回退方案。
在 CSS 中使用 @font-face 声明时,应当将 WOFF2 格式放在 src 列表的最前面,这样支持 WOFF2 的浏览器会优先加载更小的文件,而较旧的浏览器则会自动回退到 WOFF 格式。同时建议为字体文件添加 font-display: swap 属性,确保在字体加载期间文本保持可见。
实际优化案例
下面以一个真实的企业落地页优化场景为例,展示子集化技术的实际效果。
该落地页使用了一款设计感较强的中文标题字体,原始字体文件为 OTF 格式,体积 12.3MB,包含约 28000 个字形。经过内容分析,页面中实际使用了 168 个不重复汉字,加上英文字母、数字和常用标点符号,总计 247 个字符。
优化过程体积变化
优化结果非常显著:文件体积从 12.3MB 缩减到 82KB,压缩比达到 99.3%。4G 网络下的加载时间从约 6.2 秒缩短到约 40 毫秒,基本实现了即时加载。同时,由于字体文件变小,FOIT 现象也几乎不再出现,页面渲染体验得到了质的提升。
值得注意的是,这个优化方案完全不影响字体的视觉效果。子集化只是移除了未使用的字形,保留下来的字符在渲染质量上与原始字体完全一致,包括字重、字形轮廓、字距调整(kerning)和提示信息(hinting)等所有细节都被完整保留。
使用 OneKit 字体工具
OneKit 提供了一套完整的在线字体处理工具,可以帮助你快速完成字体优化工作,全部操作在浏览器本地完成,无需上传文件到服务器,充分保障字体文件的版权安全。
建议的操作流程是:先使用「字体预览」工具查看原始字体的基本信息和字形数量,再使用「字体压缩」工具输入页面文字内容进行子集化提取,最后使用「字体转换」工具将子集化后的字体转换为 WOFF2 格式。三步操作即可完成从原始字体到生产可用的优化字体的全部流程。