font-display: swap单独使用必然抖动,因其仅控制替换时机,不解决系统字体与Web字体在x-height、ascent、line-height等度量值上的偏差,需配合ascent-override、preload(带crossorigin)、中文字体子集化及显式尺寸约束才可消除布局偏移。

font-display: swap 单独用必然抖动
加了 font-display: swap 还抖,不是字体没加载完,而是浏览器先用系统字体占位、再换 Web 字体时重排了。中文字体和 PingFang SC / HarmonyOS Sans 的 x-height、ascent、line-gap 差异常超 5%,一替换就跳——标题下移、按钮换行点突变、图标错位都是典型表现。
关键点在于:swap 只管“什么时候换”,不管“换的时候怎么不跳”。它默认按后备字体度量分配空间,而你的 Web 字体实际撑开的区域不同。
- 同一字体族的每个字重(
400、500、700)必须各自写一条@font-face,并都带font-display: swap,否则部分粗体直接 fallback 到过粗的系统字体 -
format("woff2")必须显式声明,旧版 Safari 和某些 WebView 不认省略格式的src - 别信“本地不抖线上抖”——本地有缓存,首次加载的 metrics 差异被掩盖了;线上弱网下抖得更明显
ascent-override 才是真正压住抖动的核心
font-size-adjust 只调 x-height,Safari 还不支持;size-adjust 要求 Chromium/Safari 17.4+,且只对部分字体生效。真正能精准对齐基线的是 ascent-override、descent-override 和 line-gap-override,它们直接覆盖字体原始度量值,让系统字体“假装”和你的 Web 字体有一样的基线结构。
操作路径很明确:
立即学习“前端免费学习笔记(深入)”;
- 在 Chrome DevTools → Elements → Computed 面板里选中目标文本,展开 Font metrics,记下
ascent(如1024)、descent(如-256)、line-gap(如0) - 把数值转成百分比:比如
ascent-override: 100%(相对font-size),descent-override: -25% - 必须加
font-optical-sizing: none,否则光学缩放会干扰 override 效果
preload 必须带 crossorigin 才能复用
光写 font-display: swap 没用——如果字体请求还没发出,500ms 窗口早就过了。<link rel="preload"> 是唯一能在 HTML 解析阶段就拉取字体的方式,但漏掉 crossorigin 就等于白写。
-
<link rel="preload" href="/fonts/Inter-Bold.woff2" as="font" type="font/woff2" crossorigin>——crossorigin属性不能省,哪怕字体同源 -
href路径必须和@font-face中的src完全一致(包括查询参数),否则预加载资源无法复用,浏览器会再发一次请求 - 不加
as="font"或type="font/woff2",浏览器当普通资源处理,不提优先级,加载延迟更明显
中文字体不子集化,其他优化全白搭
完整中文字体普遍 2–5MB,swap 在 3G 下要等 2–3 秒才替换,期间系统字体撑开的空间和实际 Web 字体偏差极大。子集化不是“锦上添花”,是硬门槛。
实操要点:
- 按页面真实用到的汉字生成子集(比如首页只用「联系我们」「婚礼策划」「预约咨询」等 200 字),体积压到
200KB以内 - 在
@font-face中加size-adjust: 102%或类似值(需实测),微调后备字体占位比例 - 容器高度、
line-height、padding尽量用固定值或rem,避免字体替换引发重排 - 别指望
font-display: optional—— 它要求 100ms 内加载完成,中文字体子集也难达标,且 Safari 14.1 前直接退化为block白屏
真正容易被忽略的是:所有这些设置必须同时生效才稳。少一个环节,比如漏了 crossorigin 或没做子集,抖动就会回来。尤其要注意 iOS Safari 对 ascent-override 的支持是从 17.4 开始的,低于这个版本得靠 size-adjust + 更保守的 fallback 链兜底。


















