阅读提要
font-display 的四个值决定了一件事:字体没到时,文字是「看不见」「先用系统字体」还是「干脆不换」。选错的表现是白屏或闪烁,而这两种体验的取舍取决于文案长度。
字体为什么会挡住文字
浏览器加载自定义字体时,有三个时间段:
- 阻塞期:字体还没下载完,使用该字体的文字不显示。默认最多 3 秒。
- 交换期:字体仍然没到,先用后备字体显示,字体到了再替换。默认无限期。
- 失败期:超过阻塞期和交换期,浏览器放弃,之后即使字体到了也不换。
如果什么都不配,浏览器走阻塞期——这 3 秒里你的标题是空白的。这就是「白屏」的来源。
font-display 就是用来调整这三个阶段的。

四个值的真实差异
block:长阻塞期(约 3 秒),短交换期。文字最多白 3 秒,之后要么换字体要么直接显示后备。
swap:极短阻塞期(约 0-100ms),无限交换期。几乎立刻用后备字体显示,字体到了再换。代价是会闪一下(FOUT,无样式文本闪烁)。
fallback:极短阻塞期 + 短交换期(约 3 秒)。3 秒内到了就换,没到就放弃。
optional:极短阻塞期 + 无交换期。浏览器自己判断——网络慢就直接不用这个字体,本次访问不换。
选择的关键是文字量:
- 标题、按钮等短文案:
swap的闪烁几乎察觉不到,优先保证不白屏。 - 正文长文本:整段替换会非常刺眼,
optional或fallback更合适。
中文字体的特殊性
一个中文字体文件动辄 3-10MB。这带来两个后果:
一是加载慢。 在 4G 网络下,3MB 大概要 2-5 秒。这段时间里 swap 意味着用户先看到系统字体,然后整页文字重排——非常难看。
二是子集化几乎是必须的。 常用汉字 3500 个左右,覆盖 99% 的日常内容。用工具(fonttools、subfont)把字体裁剪到只含用到的字形,体积能降到 200-500KB。
pyftsubset myfont.ttf \
--text-file=used-chars.txt \
--output-file=myfont-subset.woff2 \
--flavor=woff2
更进一步是按 unicode-range 切片:把常用字、次常用字、生僻字分成几个文件,浏览器只下载需要的部分。Google Fonts 的中文字体就是这么做的。
预加载:把关键字体提到最前
<link rel="preload" href="/fonts/main.woff2" as="font" type="font/woff2" crossorigin>
crossorigin 不能省。字体请求是 CORS 请求,缺了这个属性,浏览器会下载两次(一次 preload、一次实际使用)。
但 preload 要克制:只 preload 首屏真正用到的那一个字体文件。preload 三个字体等于把关键路径堵住,反而更慢。
一个务实的组合
@font-face {
font-family: 'SiteFont';
src: url('/fonts/site-subset.woff2') format('woff2');
font-display: swap;
unicode-range: U+4E00-9FFF, U+3000-303F, U+FF00-FFEF;
}
body {
font-family: 'SiteFont', -apple-system, 'PingFang SC', 'Microsoft YaHei', sans-serif;
}
三个要点:
- 子集化后的 woff2,控制在 500KB 内;
swap保证不白屏;- 后备字体链要写全,不同系统落到不同字体,但至少都是清晰可读的。
常见的错误做法
只用 font-display: block 图省事。 结果在弱网下白屏 3 秒,用户以为站点挂了。
把 font-face 写在 CSS 文件里但用 JS 动态插入。 字体请求被推迟到 JS 执行后,首屏必然白字。
用 document.fonts.ready 做淡入。 看似优雅,实际是「先白屏,再淡入」,比直接 swap 更慢。
忽略 local() 回退。 如果用户本地已经装了这个字体(比如系统自带),加一行 local('FontName') 可以完全跳过下载。
检测方法
document.fonts.ready.then(() => {
console.log('fonts loaded')
})
配合 Performance 面板的「Font」筛选,能看到每个字体文件的下载耗时和触发时机。如果发现某个字体在首屏渲染之后才加载,说明它没有出现在关键路径上——要么 preload,要么接受它晚到。
收尾
字体策略没有统一答案,只有取舍:
- 想「绝不白屏」→
swap,接受闪烁; - 想「绝不闪烁」→
optional,接受部分用户看不到自定义字体; - 想「精确控制」→ 子集化 + preload +
swap,把文件压到足够小,闪烁短到察觉不到。
第三条是多数站点的最优解,前提是愿意做子集化这一步。
你认为这篇文章怎么样?
还没有表情反馈

一起聊聊
一个想法、一点补充,或一句简单的问候。
评论与回复
友善交流,彼此尊重第一句话,留给你。
读完有什么感受?欢迎成为第一个分享的人。