症状
这种偏移有用户看得见的剧情:标题先以系统字体出现,web 字体到达时跳高一行并把下方内容整体推下去。DevTools 录制机械地确认它——LayoutShift score 0.12,来源是覆盖首屏的 hero 标题;网络面板排出时间线:woff2 在首绘后 1.8 秒完成,且是 swap 配置。度量差就是物理成因:回退字体窄 5%,每一行换行都变、下面每个块都继承位移,CLS 逐块累积。Lighthouse CLS 超 0.05 且偏移条目指向文本元素,是数据侧指纹。
诊断思路
先用 Layout Shift 条目定位偏移元素与影响分数;再看网络面板里字体文件相对首绘的到达时间,两态并存的时长决定感知偏移;最后对比两字体的度量(步进宽度、行高),量化每次 swap 引发的重排。多个字重渐进加载、每个 swap 都推一次相关文本,是放大器。
根因
font-display: swap 下浏览器先用回退字体绘制,web 字体到达后替换。两个字面度量不同——步进宽度与行高不同——每一行都重新换行、每个块都改变尺寸,下方内容被推移,CLS 累积。
修复与验证
预加载关键字体让 swap 提早发生;用 size-adjust、ascent-override、descent-override、line-gap-override 把回退字体调到与目标度量一致(next/font 自动化了同一技术);保持 font-display: swap 让文本全程可见;unicode-range 子集化砍字节,甚至把 swap 挪到首绘之前彻底消失。验证时 Lighthouse CLS 回到 0.05 以下、文本类 Layout Shift 条目消失、限速网络回放中字体切换视觉上不可感知。用长时间 font-display: block 挡住文本渲染是把 CLS 换成不可见文本加更差的 LCP——匹配度量才是正解。
代码示例
LayoutShift: score 0.12
sources: <h1 class="hero"> ... impact region covering the fold
<link rel="preload" href="/fonts/inter-var.woff2"
as="font" type="font/woff2" crossorigin>
@font-face {
font-family: 'Fallback';
src: local('Arial');
size-adjust: 100.6%; /* tuned to Inter advance width */
ascent-override: 90%;
descent-override: 22%;
line-gap-override: 0%;
}
body { font-family: Inter, Fallback, sans-serif; }
@font-face {
font-family: Inter;
src: url(/fonts/inter-var.woff2) format('woff2-variations');
font-display: swap;
}