网页字体的性能困境
网页字体(Web Font)是现代网站设计中不可或缺的元素,它能显著提升品牌的视觉识别度和页面的设计质感。然而,字体文件往往是页面中体积较大的资源之一——一个完整的字体族(包含常规、粗体、斜体等多个字重)总大小可能达到 1-2MB,中文字体文件更是动辄 5-10MB。
未优化的字体加载会导致 FOIT(Flash of Invisible Text,文字不可见闪烁)或 FOUT(Flash of Unstyled Text,无样式文字闪烁)现象,严重影响用户体验和 Core Web Vitals 中的 LCP 指标。根据 Google 的研究,字体加载优化可以使 LCP 平均提升 15-20%。本文将详细介绍网页字体的优化加载策略。
举个直观的例子:一个同时引入 400、700 两个字重、外加斜体的英文网站,字体文件合计通常超过 1MB;而中文字体因为包含数万个字形,一个思源黑体 Regular 的单字重 woff2 就有 4-5MB。把这些文件不加处理地挂在页面上,首屏渲染就会被严重拖慢。接下来的策略都围绕同一个目标:让“该用的字尽快用上、不该用的字一个都不下载”。
一、字体加载的核心概念
1.1 字体显示策略
CSS 提供了 font-display 属性来控制字体加载期间的文字显示行为:
| 属性值 | 行为 | 适用场景 |
|---|---|---|
auto |
浏览器默认策略(通常是 FOIT) | 通用场景 |
block |
短暂阻塞(~3s 不可见,之后降级) | 品牌字体关键使用 |
swap |
立即显示降级字体,加载后替换 | 大多数场景(推荐) |
fallback |
短暂阻塞,若超时则长期使用降级字体 | 平衡美观与性能 |
optional |
极短阻塞,若超时则不再加载 | 追求极致性能 |
@font-face {
font-family: 'MyFont';
src: url('/fonts/myfont.woff2') format('woff2');
font-display: swap; /* 推荐大多数场景使用 */
}
1.2 字体格式对比
| 格式 | 压缩率 | 浏览器支持 | 说明 |
|---|---|---|---|
| WOFF2 | 最佳 | 现代浏览器均支持 | 首选格式 |
| WOFF | 好 | 广泛支持 | 兼容旧浏览器 |
| TTF/OTF | 无压缩 | 全支持 | 不推荐用于 Web |
| EOT | 一般 | 仅 IE | 已淘汰 |
实际工程中,同一款字体用不同格式保存,体积差异非常明显。以思源黑体(Source Han Sans)简体中文 Regular 字重为例:TTF 原始文件约 16MB,转成 WOFF 约 10MB,再转成 WOFF2 约 4-5MB。如果只子集化到常用汉字,woff2 可以压到几百 KB。这也是为什么“WOFF2 + 子集化”几乎成为中文字体优化的标准组合。
二、优化策略
2.1 字体子集化
对于中文字体,子集化是最有效的优化手段。中文字体包通常包含 20000+ 字符,但一个典型网站实际使用的字符不到 2000 个。通过子集化可以移除未使用的字符,将字体文件缩小 90% 以上:
# 使用 glyphhanger 提取页面使用的字符
glyphhanger --whitelist="常用汉字和标点" --subset=*.ttf --format=woff2
如果不依赖 glyphhanger,用开源工具 pyftsubset(来自 fonttools)也能完成同样的工作:
pyftsubset SourceHanSansSC-Regular.otf \
--text-file=chars.txt \
--output-file=subset.woff2 \
--flavor=woff2 \
--layout-features='*'
其中 chars.txt 是页面实际用到的字符列表,可以从构建产物中提取。对纯中文内容,通常保留 3000-5000 个字符就能覆盖绝大多数页面;加上 unicode-range 分段后,浏览器可以按需只下载含目标文字的片段。
2.2 预加载关键字体
<link rel="preload" href="/fonts/main.woff2" as="font" type="font/woff2" crossorigin>
2.3 字体加载策略组合
| 方案 | 复杂度 | 性能 | 可维护性 | 适用场景 |
|---|---|---|---|---|
| font-display: swap | 低 | 高 | 高 | 所有项目 |
| 字体子集化 | 中 | 极高 | 中 | 中文/日文等大字符集 |
| 预加载关键字体 | 低 | 高 | 高 | 首屏文字 |
| 内联关键字体 | 高 | 极高 | 低 | 首屏字体极少时 |
| 自托管字体 | 中 | 高 | 中 | 避免第三方依赖 |
2.4 自托管 vs 第三方字体服务
<!-- Google Fonts 加载(不推荐用于性能敏感项目)-->
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;600&display=swap" rel="stylesheet">
<!-- 自托管时只需使用 @font-face -->
<style>
@font-face {
font-family: 'Inter';
src: url('/fonts/inter-regular.woff2') format('woff2');
font-display: swap;
unicode-range: U+0000-00FF; /* 限制 Unicode 范围 */
}
</style>
如果因为团队协作等原因暂时保留第三方字体服务,至少用 preconnect 提前建立连接,让浏览器更早发出请求:
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
自托管虽然增加了一点维护成本,但能完全控制缓存、font-display 和真正下发哪些字重,在高延迟网络下收益尤其明显。
三、代码示例
// 使用 Font Face Observer 检测字体加载完成
import FontFaceObserver from 'fontfaceobserver';
const font = new FontFaceObserver('MyFont');
font.load(null, 3000).then(() => {
document.documentElement.classList.add('fonts-loaded');
}).catch(() => {
document.documentElement.classList.add('fonts-failed');
});
/* 字体加载前使用系统字体 */
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
}
/* 字体加载后切换 */
.fonts-loaded body {
font-family: 'MyFont', sans-serif;
}
四、注意事项
- FOUT 比 FOIT 更好:用户宁愿先看到系统字体的文字,也不愿看到空白
- 限制字重数量:每增加一个字重(bold、semibold 等)都意味额外的下载
- 使用 variable fonts:可变字体一个文件包含多个字重,大幅减少请求数
- 缓存策略:字体文件应设置长缓存时间(一年以上),因为字体很少更新
五、字体加载的常见问题
为什么字体加载了但页面还是用了系统字体? 最常见的原因是 font-display: swap 超时或字体文件未随页面成功下载——检查 Network 面板里字体请求是否返回 200,以及 @font-face 的 src 路径是否与部署目录一致。
为什么中文字体页面首屏特别慢? 中文字体动辄数 MB,问题基本都出在“整包加载”。先做子集化,再配合 unicode-range 分段加载,首屏只下载包含标题、按钮等关键文字的片段,通常能把体积从 5MB 降到 500KB 以内。
如何验证字体是否真的加载完成了? 打开 Chrome DevTools → Network 过滤 Font,可以看到每个字体文件的下载大小、耗时与优先级;用 Performance 面板录制页面加载,能直观看到字体阻塞首屏渲染的时间段。
参考:MDN font-display https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/font-display;Google web.dev 字体优化指南 https://web.dev/articles/font-best-practices;fonttools/pyftsubset https://fonttools.readthedocs.io/en/latest/subset/index.html
六、总结
网页字体优化需要在设计品质和加载性能之间找到平衡点。建议从最基础的措施开始——使用 WOFF2 格式、设置 font-display: swap、预加载关键字体。对于中文等大字符集字体,子集化几乎是必须的优化步骤。通过组合使用这些策略,你可以在不影响品牌视觉效果的前提下,将字体加载对页面性能的影响降到最低。