font-display: swap 是多数场景下的实际起点,需正确写入 @font-face 内部、避免拼错值、为不同字重单独声明,并配合 preload + crossorigin 及合理 font-family 回退链使用。

font-display: swap 是多数场景下的实际起点
不是“要不要加”,而是加了但选错值,反而让移动端用户多等3秒白屏。Chrome 对未声明 font-display 的字体默认按 auto 处理,行为接近 block:文字区域留空、不渲染、不 fallback,直到字体加载完成或超时(通常 3 秒)。而 swap 强制浏览器立刻用 font-family 回退链里的第一个可用系统字体撑开布局,自定义字体加载完再替换——有轻微重排,但用户至少看得见字。
容易踩的坑:
-
font-display: swap必须写在@font-face规则内部,写在body或全局 CSS 里完全无效 - 拼错值(比如
swop、swap:)会导致整个@font-face被浏览器忽略,连 fallback 都没机会触发 - 同一字体族的不同字重(如 400 和 700)必须各自声明
@font-face并单独配font-display,否则部分字重会回退失败
为什么 font-display: optional 在响应式页面里基本不能用
optional 要求字体在 100ms 内加载完成才启用,否则全程 fallback —— 这在 3G、弱 Wi-Fi 或低端 Android 设备上几乎不可能达成。更麻烦的是,Safari 14.1 之前版本不支持它,会直接退化为 block,导致 iPad mini(A12 + iOS 15)这类设备白屏。
它只适合一种情况:非关键装饰性字体,且你已用 @supports (font-display: optional) 做了降级兜底。例如:
立即学习“前端免费学习笔记(深入)”;
@supports (font-display: optional) {
@font-face {
font-family: 'BrandDisplay';
src: url('/fonts/brand-display.woff2') format('woff2');
font-display: optional;
}
}
别把它用在正文、按钮、表单标签这类用户必须读取的内容上。
preload + crossorigin 是 font-display 发挥作用的前提
font-display 控制的是“怎么显示”,不是“怎么加载”。如果字体本身加载慢,swap 也救不了首屏体验。这时候必须配合 <link rel="preload"> 提前触发请求,但顺序和属性稍错就失效:
-
<link rel="preload">标签必须出现在所有引用该字体的 CSS<link rel="stylesheet">之前,否则浏览器可能已发起重复请求 - 必须带
as="font"和type="font/woff2",否则 Chrome 不会复用预加载资源 - WOFF2 字体属于跨域资源,
crossorigin属性缺一不可,否则控制台报 CORS 错误,preload 白做 - HTML 中
href路径必须和@font-face里的url()完全一致(包括大小写、斜杠方向)
font-family 回退链不是摆设,它直接影响 swap 的观感
swap 的视觉代价是布局偏移(CLS),但偏移程度取决于系统字体和自定义字体的度量差异。如果回退链写成 font-family: 'InterVar', sans-serif;,某些 Android 设备会 fallback 到过粗的 Roboto,而旧版 iOS 可能用极细的 Helvetica,导致文字突然变胖或变瘦。
稳妥写法是显式列出各平台主力系统字体,并按优先级排序:
font-family: 'InterVar', -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Noto Sans', sans-serif;
还要注意:
- 高 DPR 屏幕(如 iPhone 15 Pro)上,若 WOFF2 没做子集切分,即使
swap也会掩盖不了首屏渲染后字体突然变重的跳变 - 中文 WebFont 仍建议慎用——字符集大、体积重,
swap只能缓解白屏,无法解决加载延迟本身


















