mobile wallpaper 1mobile wallpaper 2mobile wallpaper 3mobile wallpaper 4
1486 字
4 分钟
中文字体 Web 性能救星:从 20MB 到 200KB,全自动字体子集化(Font Subset)与 AST 文本收集工程化实践

在现代网页设计中,引入一款优雅独特的品牌字体(如圆体、毛笔书法体或精致的衬线体)能够瞬间大幅提升站点的视觉高级感与辨识度。

然而,在西文排版体系中,开发者往往只需要引入 26 个英文字母加上常用标点符号,整个字体文件(WOFF2 格式)通常仅有 30KB ~ 80KB,在浏览器端完全可以做到瞬间秒开。

但当场景切换到 中文字体(CJK) 时,情况便发生了灾难性的转变:

  • 中国《通用规范汉字表》收录了 8,105 个常用汉字;
  • GB2312 编码覆盖 6,763 个汉字;
  • GBK 达到 21,003 个汉字;
  • 最新的 GB18030 更是超过了 70,000 个汉字。

一套完备的中文字体源文件(OTF / TTF)动辄高达 15MB ~ 35MB。即便使用最高压缩比的 WOFF2 格式,体积依然在 8MB ~ 15MB 之间。

把如此庞大的体积推给公网用户,会直接引发严重的 FOIT(Flash of Invisible Text,文字隐形白屏) 与 FOUT(Flash of Unstyled Text,无样式文字闪烁),将网页的 LCP(Largest Contentful Paint) 核心性能指标直接砸至谷底。

为了在“视觉独特性”与“极致性能”之间取得完美平衡,字体子集化(Font Subset) 成为了现代大型内容站点与独立博客的核心技术方案。

本文将拆解如何构建一套全自动化的静态文本扫描提取 + 字体深度剪枝压缩管线,将几十兆的巨型中文字库精准压缩至 200KB 以内。


1. 字体子集化原理:剔除沉默的大多数#

字体文件内部由多张标准 OpenType 表(如 glyf 轮廓表、cmap 字符码点到字形索引映射表、hmtx 水平度量表等)组成。

在一座拥有 2 万字形的字体库中,一个技术博客或商业宣传站全站所使用的全部文字、数字与符号往往只有 1,200 ~ 2,500 个独立字符。这意味着:字体文件中超过 90% 的字形数据从未在屏幕上渲染过一次,却让每一个访客耗费了数兆的带宽去下载!

┌────────────────────────────────────────────────────────────┐
│ 原始中文字库 (约 21,000 字形 / 体积 ~25 MB) │
│ ┌───────────────┐ ┌──────────────────────────────────────┐ │
│ │ 站点真实使用字形│ │ 90% 以上从未被访问的冷门汉字与生僻字形 │ │
│ │ (约 1,800 字) │ │ (约 19,200 字形,纯属带宽浪费) │ │
│ └───────┬───────┘ └──────────────────────────────────────┘ │
└─────────┼──────────────────────────────────────────────────┘
│ 经过动态文本提取与字体剪枝 (Font Subset)
▼
┌────────────────────────────────────────────────────────────┐
│ 纯净子集化字体 (仅保留该站点实际所需码点与度量信息) │
│ 经过 Brotli 极致压缩为 WOFF2: 体积 ~210 KB (体积缩减 99.1%) │
└────────────────────────────────────────────────────────────┘

字体子集化的核心工作就是:重新构建一张只包含目标字符集的极简 cmap 与 glyf 拓扑,将多余的轮廓数据物理抹去,并重新计算紧凑的度量表。


2. 自动化文本收集器:避免手动维护的梦魇#

如果靠人工去统计“网站到底用了哪些字”,维护成本将不可承受,且稍有不慎文章更新了新词汇,页面就会因为漏字而回退到系统默认的衬线或无衬线字体,甚至显示为难看的 .notdef 方块(豆腐块)。

我们必须在持续集成(CI)或本地构建的前置步骤中,通过脚本全自动抓取整站所有展示文本:

scripts/collect-site-characters.mjs
import fs from "node:fs";
import path from "node:path";
const SCAN_DIRS = ["src", "public"];
const EXTENSIONS = new Set([".md", ".astro", ".html", ".json", ".ts", ".js", ".vue", ".svelte"]);
// 基础保底字符集 (ASCII 可见字符 + 常用标点符号,防止动态时间/日期漏字)
const BASE_CHARS = `
0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ
~`!@#$%^&*()-_=+[{]}\\|;:'",<.>/?·!¥…()—【】、;:'“”,。、《》?
`;
const collectedSet = new Set(BASE_CHARS.replace(/\s+/g, ""));
function scanDirectory(dirPath) {
const entries = fs.readdirSync(dirPath, { withFileTypes: true });
for (const entry of entries) {
const fullPath = path.join(dirPath, entry.name);
if (entry.isDirectory()) {
if (entry.name !== "node_modules" && entry.name !== ".git" && entry.name !== "dist") {
scanDirectory(fullPath);
}
} else if (EXTENSIONS.has(path.extname(entry.name).toLowerCase())) {
extractCharactersFromFile(fullPath);
}
}
}
function extractCharactersFromFile(filePath) {
const content = fs.readFileSync(filePath, "utf8");
// 剥离代码块中的大段机器代码 (可选,如果代码块不需要应用该艺术字体)
const cleanContent = content.replace(/```[\s\S]*?```/g, "");
for (const char of cleanContent) {
// 过滤掉不可见控制符与多余空白
const code = char.charCodeAt(0);
if (code > 31 && code !== 127) {
collectedSet.add(char);
}
}
}
// 执行全站扫描
console.log("开始扫描全站字符...");
for (const dir of SCAN_DIRS) {
if (fs.existsSync(dir)) scanDirectory(dir);
}
// 排序后去重写入中间文件
const sortedChars = Array.from(collectedSet).sort().join("");
const outputPath = path.resolve("temp-site-glyphs.txt");
fs.writeFileSync(outputPath, sortedChars, "utf8");
console.log(`扫描完成!共提取出 ${collectedSet.size} 个唯一字符,已保存至 ${outputPath}`);

3. 基于 Python fonttools 的深度剪枝与 WOFF2 压缩#

提取出 temp-site-glyphs.txt 之后,我们利用目前工业界最权威的字体处理引擎 fonttools 的 subset 模块进行无损剥离。

fonttools subset 提供了极其细致的优化参数:

# 执行子集化剪枝命令
pyftsubset "src/assets/fonts/OriginalChineseFont.ttf" \
--text-file="temp-site-glyphs.txt" \
--output-file="src/assets/fonts/ChineseFont.subset.woff2" \
--flavor=woff2 \
--layout-features='*' \
--desubroutinize \
--no-hinting

关键参数深度解析:#

  1. --flavor=woff2:直接输出现代浏览器首选的 WOFF2 封装格式,它在底层集成了 Google Brotli 压缩算法;
  2. --no-hinting:剔除字体内的字节码渲染微调指令(Hinting)。在现代视网膜(Retina / 4K)高清屏幕以及浏览器内置的亚像素抗锯齿引擎下,传统 Hinting 已经很少发挥作用,剔除它可以直接削减 15% ~ 25% 的体积;
  3. --desubroutinize:在 CFF/OTF 轮廓中展开子程序调用,通常能让 Brotli 取得更高的词频重复率与更好的压缩比;
  4. --layout-features='*':保留常用的字形连字(Ligatures)与字偶间距(Kerning),确保文字排版的美感不打折。

4. 前端工程化集成与 NPM 构建生命周期#

为了让这一过程完全无感,我们可以在前端工程的 package.json 中将其编排进自动化构建链路:

{
"scripts": {
"subset-fonts": "node scripts/collect-site-characters.mjs && node scripts/subset-site-fonts.mjs",
"prebuild": "npm run subset-fonts",
"build": "astro build"
}
}

在 CSS 中,通过标准 @font-face 声明并指定 font-display: swap:

@font-face {
font-family: 'CustomChineseSerif';
src: url('/fonts/ChineseFont.subset.woff2') format('woff2');
font-weight: 500;
font-style: normal;
/* 确保字体在网络加载期间使用系统默认字体占位,加载完毕后优雅替换,彻底杜绝白屏 */
font-display: swap;
}
body {
font-family: 'CustomChineseSerif', -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
}

5. 优化成效与网络加载瀑布流对比#

以一套包含 2.3 万字形的商业艺术字体为例,经过自动化子集化管线后的数据变化如下:

阶段格式文件体积4G 弱网下载耗时 (预估)页面渲染行为 (FOIT)
原始完整字库TTF22.4 MB> 45 秒 (几乎无法完成)严重白屏,访客大概率直接关闭标签页
全量 WOFF2 转换WOFF211.2 MB~ 22 秒漫长空白,LCP 评分为 0
子集化裁剪 (1,850 字)TTF1.1 MB~ 2.2 秒较短闪烁
子集化 + Brotli 压缩WOFF2~ 218 KB< 350 ms秒开就绪,Core Web Vitals 全绿

从 22.4 MB 暴降至 218 KB,压缩比达到了惊人的 99.02%!


6. 总结#

在中文 Web 开发中,“追求字体设计美感”与“保障极速加载性能”从不是一道单选题。

通过在工程底层搭建一套 “AST 源码扫描 ➔ 字符码表去重 ➔ fonttools 拓扑重构 ➔ WOFF2 深度压制” 的自动化流水线,我们用严谨的工程思维击碎了中文字库庞大臃肿的传统难题,让精美的高级中文字体在 Web 端也能轻盈如燕、瞬时绽放。

分享

如果这篇文章对你有帮助,欢迎分享给更多人!

中文字体 Web 性能救星:从 20MB 到 200KB,全自动字体子集化(Font Subset)与 AST 文本收集工程化实践
https://blog.luozili.work/posts/cjk-font-subset-ast-text-collector/
作者
llbzow
发布于
2026-10-20
许可协议
CC BY-NC-SA 4.0

部分信息可能已经过时

相关文章 智能推荐
1
Web 图像算法实战:纯前端 Canvas 实现“幻影坦克”(黑白底双重图)的像素级 Alpha 逆向求解
前端与图形算法 深度拆解在现代 Web 浏览器中纯前端实现“幻影坦克”(即一张 PNG 图片在白色背景与黑色背景下分别显示两张完全不同画面)的图像混合数学原理。从 Porter-Duff 经典线性混合方程推导,到利用联立方程逆向求解像素级目标透明度 Alpha 与基底色彩,再到动态直方图色彩区间压缩与 Canvas Uint32Array 像素流水线优化。
2
Vue 3 响应式系统深潜:WeakMap 依赖拓扑、ReactiveEffect 副作用调度与微任务批量更新
Vue学习之路 承接站内 Vue 系列进阶手记,深度下潜至 Vue 3 响应式微内核底层。全面拆解 Proxy 配合 Reflect 解决 getter 作用域代理丢失的机理、全局 targetMap 的 WeakMap-Map-Set 三层依赖拓扑设计、分支切换时的旧依赖清理算法,以及利用 ReactiveEffect 的 scheduler 钩子与微任务队列(jobQueue)实现防抖防重复计算的批量异步刷新机制。
3
WebGPU 现代图形计算实战:基于 WGSL 计算着色器的 10 万粒子万有引力模拟
系统架构与性能 深入解析现代 Web 浏览器的下一代图形与异构计算标准 WebGPU。告别 WebGL 时代将通用数据伪装成浮点纹理渲染到帧缓冲(FBO)的别扭方案,全面拆解 WGSL 计算着色器(Compute Shader)语法、GPUBuffer 内存布局、Ping-Pong 双缓冲消除数据竞态,以及计算管线与渲染管线零拷贝共享显存,在浏览器端稳定 60 FPS 驱动 10 万星体万有引力相互作用模拟。
4
移动端定位避坑:弧度/度识别与配置化作业区校验
技术分享 记录移动端接入 GNSS 数据时,如何自动识别弧度与角度、统一坐标单位,并通过配置化作业区校验提前拦截异常值。
5
市政道路 RTK 数据导入:断链映射与缓和曲线重建
RTK 与工程几何 解析市政道路测量 Excel/CSV 数据,建立连续桩号映射、重建缓和曲线,并对输入数据执行闭合差审计。

目录

💬
🎀