Web 字体渲染规则:font-display、阻塞期与字体交换
🔤 系统理解 Web Font 从匹配、下载到绘制的全过程,重点解释
font-display: optional、swap、阻塞期(block period)与交换期(swap period),以及它们对 FOUT、FOIT、CLS 和性能的影响。
一、浏览器如何完成一次字体渲染
当元素命中某个 font-family 时,浏览器大致经历以下过程:
- 字体匹配:依据
font-family、font-weight、font-style、font-stretch选择字体面。 - 检查字体状态:目标 Web Font 可能尚未请求、正在下载、已经可用或加载失败。
- 进入阻塞期:是否暂时隐藏文本,取决于
font-display策略。 - 使用后备字体:阻塞期结束而 Web Font 尚不可用时,使用 fallback 字体绘制。
- 决定是否交换:Web Font 到达后,浏览器是否把 fallback 替换为 Web Font,取决于交换期是否仍有效。
font-display不负责选择后备字体,也不直接设置下载优先级。它主要控制“等待字体多久、何时显示 fallback、字体到达后是否替换”。实际时间窗口由浏览器实现决定,规范描述的是短暂、较短或无限等行为级别,不应依赖精确毫秒数。
二、三个关键时间阶段
1. Font block period:字体阻塞期
字体尚未加载时,浏览器可能使用不可见的 fallback 占位。文本参与布局但暂时不可见,容易形成 FOIT(Flash of Invisible Text)。
2. Font swap period:字体交换期
阻塞期结束后先显示 fallback;如果 Web Font 在交换期内加载完成,就替换字体。替换时字符宽度、字高和基线可能变化,从而造成布局偏移。
3. Font failure period:字体失败期
交换期结束后仍未加载完成,该字体在本次页面生命周期中被视为错过展示时机。页面继续使用 fallback;即使资源随后到达,也不一定再进行视觉替换。
三、font-display 各取值对比
| 取值 | 阻塞期 | 交换期 | 用户体验倾向 | 常见场景 |
|---|---|---|---|---|
auto | 由浏览器决定 | 由浏览器决定 | 使用浏览器默认策略 | 没有明确需求时 |
block | 较长 | 无限 | 优先保证最终使用品牌字体,但可能长时间看不到文字 | 图标字体、极强品牌展示;正文通常不推荐 |
swap | 极短 | 无限 | 尽快显示 fallback,字体到达后必定交换 | 正文、内容优先页面 |
fallback | 极短 | 较短 | 快速显示;字体到得不够快便放弃本次交换 | 兼顾品牌与稳定性的正文 |
optional | 极短 | 无交换期 | 字体立即可读,通常不在本次渲染中交换 | 性能优先、弱网、避免布局偏移 |
常见浏览器实现中,“极短”通常约为 100ms,“较短交换期”通常约为数秒,但这些数字不是跨浏览器契约。
四、font-display: swap 的运行机制
@font-face {
font-family: "InterVariable";
src: url("/fonts/inter-var.woff2") format("woff2");
font-weight: 100 900;
font-style: normal;
font-display: swap;
}
body {
font-family: "InterVariable", system-ui, sans-serif;
}
典型过程:
- 浏览器只进行极短等待。
- 字体未就绪时立即用
system-ui绘制,避免正文长时间不可见。 - Web Font 在任意时刻加载完成后都可以替换 fallback。
- 如果两套字体度量不同,换字时可能出现重排和 CLS。
因此,swap 解决的核心问题是“内容尽快可读”,但不自动解决布局稳定性。它可能产生 FOUT(Flash of Unstyled Text):先看到后备字体,再看到 Web Font。
swap 的优点
- 文本几乎立即可见,弱网下仍可阅读。
- 最终通常能显示设计指定字体。
- 对正文和内容型网站通常比
block更安全。
swap 的风险
- 字体晚到也会交换,可能在用户阅读过程中突然变化。
- 字宽差异会改变换行位置、按钮宽度和容器高度。
- 大字体、首屏标题或复杂布局中的视觉跳动尤其明显。
五、font-display: optional 的运行机制
@font-face {
font-family: "InterVariable";
src: url("/fonts/inter-var.woff2") format("woff2");
font-weight: 100 900;
font-style: normal;
font-display: optional;
}
optional 可以理解为:只接受及时到达的字体,不为了本次展示等待,也不承诺稍后交换。
典型过程:
- 浏览器提供一个极短的阻塞窗口。
- 如果字体已在缓存中,或能在窗口内迅速可用,则直接使用 Web Font。
- 如果没有及时可用,立即使用 fallback。
- 进入 fallback 后,本次页面通常不再发生字体交换。
- 浏览器还可以结合网络状况和资源策略,决定该字体是否值得继续下载。
- 如果字体后来进入缓存,下次导航或刷新时可能直接使用。
为什么 optional 更稳定
它基本消除了“先 fallback、后 Web Font”的可见交换,因此能显著降低字体引起的 CLS,也避免用户阅读中途字体突变。
必须接受的代价
- 首次访问和弱网环境中,用户可能始终看到 fallback。
- 同一页面在不同用户、不同网络或不同缓存状态下,最终字体可能不同。
- 如果品牌字体是内容表达的一部分,
optional可能不满足设计要求。
optional 不等于“不下载字体”
optional 的重点是“字体对当前渲染是可选的”。浏览器可能继续请求并缓存字体,也可能根据连接质量降低其优先级或放弃请求;不能把它当成确定的网络取消开关。
六、fallback 与 optional 的区别
二者都会快速显示后备字体,但核心区别是:
fallback:保留一个较短的交换机会。字体在这个窗口内到达,仍会替换。optional:基本没有交换期。错过极短窗口后,优先保持当前 fallback,不再打扰用户。
可以将它们视为从“追求最终品牌字体”到“追求视觉稳定”的连续选择:
swap → fallback → optional
交换意愿逐渐降低,布局稳定性通常逐渐提高。
七、FOIT、FOUT 与 CLS
FOIT:不可见文本闪烁
字体等待期间文本不可见。block 风险最高;swap、fallback、optional 的阻塞期都很短。
FOUT:未样式化文本闪烁
先显示 fallback,再替换成 Web Font。swap 最典型,fallback 也可能发生,optional 通常避免可见交换。
CLS:累计布局偏移
字体交换并不一定产生 CLS;只有字体度量差异导致可见元素移动时才会影响布局。常见原因包括:
- 字符宽度不同,导致换行变化;
x-height、上升部和下降部不同;line-height: normal在不同字体下计算不同;- 按钮、标签等由文字撑开的组件宽度变化。
八、用字体度量覆盖降低 swap 的布局偏移
如果必须使用 swap,可以调整 fallback 字体度量,使其更接近目标字体:
@font-face {
font-family: "Inter Fallback Adjusted";
src: local("Arial");
size-adjust: 107%;
ascent-override: 90%;
descent-override: 22%;
line-gap-override: 0%;
}
@font-face {
font-family: "InterVariable";
src: url("/fonts/inter-var.woff2") format("woff2");
font-weight: 100 900;
font-display: swap;
}
body {
font-family: "InterVariable", "Inter Fallback Adjusted", sans-serif;
}
size-adjust:整体缩放字形和度量。ascent-override:覆盖上升部比例。descent-override:覆盖下降部比例。line-gap-override:覆盖字体建议行间距。
这些数值必须根据目标字体与 fallback 的真实度量计算,不能机械复制示例。
九、预加载与 optional 的配合
<link
rel="preload"
href="/fonts/inter-var.woff2"
as="font"
type="font/woff2"
crossorigin
/>
预加载可以让关键字体更早开始下载,提高它在极短窗口内就绪的概率。但要注意:
- 只预加载首屏真正会用到的少量字体。
href、字体格式和crossorigin必须与@font-face请求一致,否则可能重复下载。- 预加载提高网络优先级,却不能保证赶上
optional的展示窗口。 - 过多字体预加载会与 CSS、脚本、图片争抢带宽,反而损害 LCP。
十、选择策略
优先使用 swap
适用于正文、新闻、文档和必须最终呈现品牌字体的页面。搭配度量覆盖和明确 line-height 控制 CLS。
优先使用 fallback
适用于希望字体尽快生效,但不能接受很晚才突然变化的界面。
优先使用 optional
适用于:
- 性能和布局稳定性高于字体一致性;
- 用户可能处于弱网或高延迟环境;
- Web Font 只是渐进增强;
- fallback 已经过精心选择,视觉结果可接受。
谨慎使用 block
正文通常不应使用。图标字体也应优先评估 SVG 图标,避免字体加载失败导致图标不可见或语义混乱。
十一、实践组合
/* 品牌标题:希望最终显示品牌字体 */
@font-face {
font-family: "Brand Display";
src: url("/fonts/brand-display.woff2") format("woff2");
font-display: swap;
}
/* 正文:系统字体也完全可接受,稳定性优先 */
@font-face {
font-family: "Reading Text";
src: url("/fonts/reading-text.woff2") format("woff2");
font-display: optional;
}
h1 {
font-family: "Brand Display", Georgia, serif;
line-height: 1.1;
}
body {
font-family: "Reading Text", system-ui, sans-serif;
line-height: 1.6;
}
同一站点不必只有一种策略。可以按字体角色、首屏重要性、布局敏感度和品牌要求分别选择。
十二、调试与验证清单
- 在 DevTools Network 中禁用缓存,模拟首次访问。
- 分别测试 Fast 3G、Slow 3G、离线和高延迟环境。
- 观察字体请求的发起时间、优先级、缓存命中与完成时间。
- 检查首屏是否出现 FOIT 或 FOUT。
- 使用 Performance / Web Vitals 观察字体交换时的 Layout Shift。
- 对比目标字体与 fallback 的换行、按钮宽度和行高。
- 确认
@font-face的 weight/style 范围与实际使用一致,避免伪粗体或额外请求。 - 确认 preload URL、CORS 模式和
@font-face src完全匹配。 - 测试冷缓存、热缓存和重复导航,不能只看本地热缓存效果。
十三、常见误区
- “****
swap一定更快”:它只是更快显示文本,字体文件本身不会因此下载得更快。 - “****
optional一定不请求字体”:是否继续下载由浏览器与网络策略决定。 - “Web Font 到达就一定会替换”:
optional或已过交换期的fallback通常不会在本次展示中替换。 - “使用
swap就解决了性能问题”:它缓解 FOIT,但可能带来 FOUT 和 CLS。 - “预加载越多越好”:不关键的字体会抢占首屏关键资源带宽。
- “设置相同字号就不会偏移”:不同字体的真实度量、字宽和行高仍可能明显不同。
✅ 决策原则:内容必须立即可读时避免长阻塞;字体必须最终出现时使用
swap;布局稳定和弱网体验优先、且后备字体可接受时使用optional;想在两者之间折中时使用fallback。