跳到主要内容

Web 字体渲染规则:font-display、阻塞期与字体交换

🔤 系统理解 Web Font 从匹配、下载到绘制的全过程,重点解释 font-display: optionalswap、阻塞期(block period)与交换期(swap period),以及它们对 FOUT、FOIT、CLS 和性能的影响。

一、浏览器如何完成一次字体渲染

当元素命中某个 font-family 时,浏览器大致经历以下过程:

  1. 字体匹配:依据 font-familyfont-weightfont-stylefont-stretch 选择字体面。
  2. 检查字体状态:目标 Web Font 可能尚未请求、正在下载、已经可用或加载失败。
  3. 进入阻塞期:是否暂时隐藏文本,取决于 font-display 策略。
  4. 使用后备字体:阻塞期结束而 Web Font 尚不可用时,使用 fallback 字体绘制。
  5. 决定是否交换: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;
}

典型过程:

  1. 浏览器只进行极短等待。
  2. 字体未就绪时立即用 system-ui 绘制,避免正文长时间不可见。
  3. Web Font 在任意时刻加载完成后都可以替换 fallback。
  4. 如果两套字体度量不同,换字时可能出现重排和 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 可以理解为:只接受及时到达的字体,不为了本次展示等待,也不承诺稍后交换。

典型过程:

  1. 浏览器提供一个极短的阻塞窗口。
  2. 如果字体已在缓存中,或能在窗口内迅速可用,则直接使用 Web Font。
  3. 如果没有及时可用,立即使用 fallback。
  4. 进入 fallback 后,本次页面通常不再发生字体交换。
  5. 浏览器还可以结合网络状况和资源策略,决定该字体是否值得继续下载。
  6. 如果字体后来进入缓存,下次导航或刷新时可能直接使用。

为什么 optional 更稳定

它基本消除了“先 fallback、后 Web Font”的可见交换,因此能显著降低字体引起的 CLS,也避免用户阅读中途字体突变。

必须接受的代价

  • 首次访问和弱网环境中,用户可能始终看到 fallback。
  • 同一页面在不同用户、不同网络或不同缓存状态下,最终字体可能不同。
  • 如果品牌字体是内容表达的一部分,optional 可能不满足设计要求。

optional 不等于“不下载字体”

optional 的重点是“字体对当前渲染是可选的”。浏览器可能继续请求并缓存字体,也可能根据连接质量降低其优先级或放弃请求;不能把它当成确定的网络取消开关。

六、fallbackoptional 的区别

二者都会快速显示后备字体,但核心区别是:

  • fallback:保留一个较短的交换机会。字体在这个窗口内到达,仍会替换。
  • optional:基本没有交换期。错过极短窗口后,优先保持当前 fallback,不再打扰用户。

可以将它们视为从“追求最终品牌字体”到“追求视觉稳定”的连续选择:

swapfallbackoptional

交换意愿逐渐降低,布局稳定性通常逐渐提高。

七、FOIT、FOUT 与 CLS

FOIT:不可见文本闪烁

字体等待期间文本不可见。block 风险最高;swapfallbackoptional 的阻塞期都很短。

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 完全匹配。
  • 测试冷缓存、热缓存和重复导航,不能只看本地热缓存效果。

十三、常见误区

  1. “****swap 一定更快”:它只是更快显示文本,字体文件本身不会因此下载得更快。
  2. “****optional 一定不请求字体”:是否继续下载由浏览器与网络策略决定。
  3. “Web Font 到达就一定会替换”optional 或已过交换期的 fallback 通常不会在本次展示中替换。
  4. “使用 swap 就解决了性能问题”:它缓解 FOIT,但可能带来 FOUT 和 CLS。
  5. “预加载越多越好”:不关键的字体会抢占首屏关键资源带宽。
  6. “设置相同字号就不会偏移”:不同字体的真实度量、字宽和行高仍可能明显不同。

✅ 决策原则:内容必须立即可读时避免长阻塞;字体必须最终出现时使用 swap;布局稳定和弱网体验优先、且后备字体可接受时使用 optional;想在两者之间折中时使用 fallback