font-display: swap 无法防止布局抖动,因其只控制字体替换时机而不约束度量变化;需配套 preload、精确的 ascent/descent/line-gap override 及完整字重声明才能稳定渲染。

font-display: swap 能让文字立刻可见,但单独用它几乎一定会导致布局抖动——这不是配置错了,而是它根本没被设计来解决抖动问题。
为什么加了 swap 还白屏或抖动
常见错误现象包括:移动端首次访问白屏 2–3 秒、标题加载后突然下移 3px、按钮内文字换行点跳变、CLS 报告中该元素贡献值超 0.15。
原因很直接:font-display: swap 只控制“什么时候替换字体”,不控制“替换时怎么不重排”。浏览器先用系统字体(如 PingFang SC 或 HarmonyOS Sans)撑开空间,等你的 WOFF2 加载完,立刻换成自定义字体——但两者的 ascent、descent、line-gap 差异常达 5%~12%,一换就重排。
更隐蔽的问题是:本地开发时字体已缓存,抖动被掩盖;线上弱网 + 首次加载,抖动才暴露。Safari 对 swap 的替换时机还有渲染队列延迟,有时会“闪一下再跳一下”。
立即学习“前端免费学习笔记(深入)”;
必须配套的三项实操动作
只写 font-display: swap 是半截子活。上线前这三件事缺一不可:
-
<link rel="preload">必须出现在所有<link rel="stylesheet">之前,且带crossorigin属性,否则预加载资源无法复用于@font-face—— 浏览器会再发一次请求 - 同一字体族的每个字重(如
400、500、700)都要单独写一条@font-face,并都配font-display: swap,否则粗体可能 fallback 到过粗的系统字体 -
ascent-override、descent-override、line-gap-override必须实测填写,不能估算。在 Chrome DevTools → Elements → Computed → Font metrics 里分别查系统字体和 Web 字体的原始值,再按比例换算成百分比,例如:ascent-override: calc(920 / 850 * 100%)
容易被忽略的兼容与构建细节
这些点不报错,但会让优化失效:
-
format("woff2")必须显式声明。旧版 Safari 和部分 WebView 不认省略格式的src声明 - 子集字体文件名要带内容哈希,比如
myfont-subset.a1b2c3.woff2。否则文案更新后,浏览器仍用缓存里的旧子集,漏字风险极高 -
font-optical-sizing: none必须加上。否则光学缩放会干扰override参数效果,尤其在高 DPR 屏幕上 - 如果用了
unicode-range分片,确保首屏文本涉及的所有 Unicode 区间都被覆盖,漏掉一个U+3000(中文全角空格)就可能导致个别字符 fallback 失败
真正稳的方案不是堆参数,而是把 swap 当作起点,再用 override 锁住度量、用 preload 锁住时机、用子集化锁住体积——三者少一个,抖动就还在那儿等着用户滚动到那一屏。


















