字体性能优化:font-display、子集化与可变字体

CLARA轻量论坛系统
CLARA轻量论坛系统 星耀SVIP管理员 黑卡会员
发布于 2026-09-17 20:19 ·4 浏览 ·0 回复

字体性能优化真正的大头是「少下载」而不是「晚渲染」:先用子集化 + woff2 把字体文件砍掉 90% 以上体积,再去调 `font-display`、再决定要不要上可变字体。顺序颠倒——一上来就加 `font-display: swap`——最多让文字早出现半秒,几 MB 的字体照样要传完,等于没优化。

先算账:字体的成本在传输字节

结论:一个中文字体的体积损失,比任何加载策略都值钱,先盯体积再谈策略。

一套常见中文无衬线字体,TTF 动辄 5-10MB;转成 woff2(Web Open Font Format 2,用 Brotli 压缩的网页字体格式)后大约 2-4MB;而只保留实际用到的字符后,通常能压到 20-150KB。同样的请求数、同样的网络条件,这一步就是 20-50 倍的差距,`font-display` 再优化也换不来这个量级。

所以排查顺序是:这个字体非用不可吗?→ 能子集化吗?→ 能转 woff2 吗?→ 最后才问「加载策略怎么选」。

子集化:一次砍掉 90% 体积

结论:静态文案用 `pyftsubset` 按实际字符集裁剪,UGC 场景用 `unicode-range` 分片,两种做法别混用。

静态站点最省事的做法是用 fonttools 直接裁:

pyftsubset SourceHanSansSC-Regular.otf \
  --text-file=used-chars.txt \
  --flavor=woff2 \
  --layout-features='*' \
  --no-hinting \
  --output-file=subset.woff2

`used-chars.txt` 就是页面里出现过的所有字符,可以写个脚本爬一遍模板自动生成。注意把中英文标点、全角符号、数字一起收进去,否则会出现「汉字是自定义字体、逗号是系统字体」的割裂感。

如果站点有用户生成内容(昵称、评论、帖子),静态子集必然缺字。这时改用 `unicode-range` 分片:把常用汉字按频率切成若干个文件,配合多个 `@font-face` 声明,浏览器只下载当前页面真正命中的分片。

@font-face {
  font-family: 'HS';
  src: url('/fonts/hs-common.woff2') format('woff2');
  unicode-range: U+4E00-9FFF; /* 常见汉字区间,实际按频率再细分 */
  font-display: swap;
}

中文分片的通用经验是:前 3000 常用字单独一个文件(这部分覆盖日常正文 99% 以上),剩余生僻字按 Unicode 区间切片,每片控制在 30KB 以内。

font-display:五个值对应三种场景

结论:正文用 `swap` 或 `optional`,品牌标题用 `block` 加 preload,`auto` 永远不要留。

`font-display` 是 `@font-face` 的描述符,控制字体没加载完时文字怎么显示。它本质是两个时间段——block 期(文字不可见)和 swap 期(用兜底字体先显示)——的组合:

取值block 期swap 期效果
`block`约 3s无限先空白,最长 3s 不可见,之后换字体
`swap`极短无限立刻用兜底字体显示,加载完替换(FOUT)
`fallback`约 100ms约 3s折中,短暂空白后兜底,3s 内到货才替换
`optional`约 100ms0100ms 内没到就本次不用,浏览器自行决定是否继续下载

怎么选:正文阅读区首选 `swap`(内容优先),对布局稳定性要求高的用 `optional`(配合 preload,第二次访问直接命中缓存);品牌标题、Logo 这类对字形敏感的用 `block` 加 preload,但一定要给 `font-display` 设个上限,别让文字长时间空白。

`auto` 等同于浏览器默认(多数是 `block`),行为不可控,显式写出来。

可变字体:一个文件替代多个字重

结论:需要 3 个以上字重时才上可变字体,1-2 个用静态子集更划算。

可变字体(Variable Font)把字重、字宽、倾斜等维度做成可调节的「轴」,一个文件顶替 Regular/Medium/Bold 等多份文件。声明方式:

@font-face {
  font-family: 'InterVar';
  src: url('/fonts/inter-var.woff2') format('woff2');
  font-weight: 100 900;  /* 声明支持的轴范围 */
  font-display: swap;
}

但要清楚它的代价:一个可变字体文件通常比单个静态字重大 30%-60%,比两个静态字重小,比四个静态字重小得多。所以只有站点确实用到 3 个以上字重时,它才划算。

另一个坑是可变字体 + 子集化会互相拖累。如果最终只用某一两个字重,可以用 fonttools 把它「实例化」成静态文件,体积立刻回落:

fonttools varLib.instancer inter-var.ttf wght=400 -o inter-400.ttf

两个最容易踩的收尾坑

结论:字体要么加 `crossorigin` 的 preload,要么别 preload;同时用度量覆盖压住 CLS。

`swap` 带来的最大副作用是 FOUT 引起的布局偏移(CLS)。解决办法是给兜底字体做「度量覆盖」,让它的行高、字宽尽量贴近目标字体:

@font-face {
  font-family: 'Fallback';
  src: local('Arial');
  size-adjust: 107%;
  ascent-override: 90%;
  descent-override: 22%;
  line-gap-override: 0%;
}

另外两个细节:跨域字体必须在服务器返回 `Access-Control-Allow-Origin`,否则会被 CORS 拦掉;preload 时 `<link rel="preload" as="font" type="font/woff2" crossorigin>` 的 `crossorigin` 一个字符都不能少,否则浏览器会下载两次。字体文件本身用 `Cache-Control: public, max-age=31536000, immutable` 长缓存,文件名带内容哈希即可放心永久缓存。

把整套流程串起来就是:统计字符 → `pyftsubset` 裁剪 + 转 woff2 → 按需分片或选可变字体 → 设 `font-display` 与度量覆盖 → preload 关键字体 + 长缓存。按这个顺序做,中文字体从 5MB 到 100KB 级的落地是完全可复现的。

本文转载自 Clara轻量论坛系统,原文地址:https://www.leleweb.cn/thread-452.html
转载请注明出处,版权归原作者所有。

全部回复 0

还没有回复,来抢沙发~