字体加载本身不“依赖”闪烁,但默认行为必然引发 FOIT 或 FOUT
这其实是浏览器的固有机制,并非bug,也谈不上配置错误。问题的核心在于,开发者能否从被动接受变为主动控制。

font-display: swap 必须写在 @font-face 规则内才生效
一个常见的误区是,将 font-display: swap 写在普通的CSS选择器或全局样式里。这么做完全无效。这个属性必须老老实实地待在 @font-face 规则内部,并且紧跟在 src 和 font-family 声明之后。
- 错误示范:
.headline { font-display: swap; }—— 这行代码什么也不会改变。 - 正确姿势:
@font-face { font-family: "Inter"; src: url("inter.woff2") format("woff2"); font-display: swap; } - 注意,如果漏写了
format("woff2")这样的格式声明,部分浏览器(比如一些旧版本)可能会直接忽略整条规则,font-display自然也就失效了。 - 对于拥有多个字重的字体家族,每个字重都需要独立的
@font-face声明,并且每一条都必须带上自己的font-display属性,它们不能共用。
font-display: swap 必须写在 @font-face 规则内且紧贴 src 和 font-family 才生效,漏写 format、未为多字重分别声明、preload 路径/MIME/跨域配置错误、iOS Safari 14.5 前不触发替换、fallback 字体不匹配导致重排,均会使字体加载优化失效。
preload 字体失败的三个静默原因
使用 来预加载字体是个好主意,但实际操作中,它常常因为一些细节问题而“静默失败”——浏览器不下载,也不报错,让人无从查起。
- 路径必须完全一致:
href属性里的路径,必须和@font-face中src的路径一字不差。这包括大小写、斜杠方向,甚至是查询参数。例如,如果src是url("/fonts/inter-bold.woff2?v=1"),那么href也必须带上?v=1。 - 属性必须配齐:
as="font"和type="font/woff2"这两个属性缺一不可。如果只写了as="font",Chrome 等浏览器可能会将其降级为普通资源处理,失去预加载的优先级。 - 跨域属性不可省:字体文件通常被视为匿名模式的跨域请求,因此必须加上
crossorigin属性(通常用crossorigin="anonymous")。少了它,预加载就会在跨域时失败,而且同样没有错误提示。 - 本地环境不工作:在本地直接双击打开HTML文件(
file://协议)进行测试时,preload指令会被浏览器直接忽略。务必启动一个本地HTTP服务器(比如用npx serve)来验证。
iOS Safari 14.5 之前 swap 不触发替换
这是一个比较隐蔽的“坑”。即便字体文件已经下载完成,在 iOS 14.4 及更早版本的 Safari 浏览器中,页面可能会一直卡在备用字体(fallback)上,死活不切换成你精心准备的自定义字体。这并非缓存问题,而是当时浏览器引擎的一个缺陷。
- 如何检测:可以用
document.fonts.check("1em Inter Bold")来检查,它可能返回true,但页面上的文字就是不变。 - 临时修复:手动触发一次重排(reflow),例如给
body元素切换一个没有任何样式的 class:document.body.classList.toggle("force-repaint")。 - 更可靠的方案:放弃纯CSS方案,转而使用
FontFaceAPI 进行显式加载和控制:const font = new FontFace("Inter", "url(inter-bold.woff2)"); font.load().then(() => document.fonts.add(font)); - 需要警惕的是,在老版本 iOS 上,不要过分依赖
document.fonts.ready这个 Promise,它可能会一直处于 pending 状态。
立即学习“前端免费学习笔记(深入)”;
fallback 字体选错,swap 就等于白加
font-display: swap 只解决了“要不要等”的问题,但解决不了“切换时页面会不会跳动”的问题。如果备用字体(fallback)与你目标字体的 x高度、字宽、行高等度量值差异过大,那么字体切换瞬间的段落重排将会非常明显。
- 避免风格混搭:尽量不要将衬线与非衬线字体混用为备用链。例如,
font-family: "Inter", "Georgia", serif这样的组合就很不理想。优先使用系统级的无衬线字体链:"Inter", "system-ui", -apple-system, BlinkMacSystemFont, "Segoe UI"。 - 中文备用链必须明确:对于中文字体,备用列表里必须显式包含常见的中文字体,例如:
"PingFang SC", "Hiragino Sans GB", "Microsoft YaHei"。如果只写"Helvetica", "Arial",在 Windows 或 Linux 的中文系统下,很容易一路降级到等宽字体Courier New,导致版面混乱。 - 慎用字体合成:对于标题等敏感元素,如果只加载了 Regular 字重,却在 CSS 中设置了
font-weight: 700,浏览器会尝试通过算法加粗备用字体,这通常会导致字符宽度突变,使得 FOFT(字体样式闪烁)更加明显。 - Chrome 120+ 版本支持
size-adjust
最后,也是最容易被忽略的一点:字体加载策略并非“配置完就一劳永逸”。它与整个页面的首屏渲染路径深度耦合。即便所有 CSS 配置都正确无误,如果字体文件托管在 CDN 上但 DNS 解析缓慢,或者服务器没有开启 Brotli 等高效压缩,那么 swap 的“交换窗口期”依然会被拉长——在这种情况下,再精巧的 CSS 技巧也难以挽救用户体验的损失。
