回退字体的度量差异造成重排
别名: 字体度量不匹配 · size-adjust · 换字重排 · metric mismatch
概念解释
回退字体和网页字体即使字号声明相同,字符宽度、x 高度、上升部、行距也不相同。交换发生时,文本块的 used 宽度和行盒高度跟着变,邻居被推开。这是字体度量不匹配(font metric mismatch)造成的重排,不是闪烁形式本身。FOIT 期间可能看不见字,但盒子仍按某套度量在排;FOUT 期间用户已经在读回退字,交换那一帧会把已读的行重新折一遍。布局偏移来自度量差,不来自“换了一张脸”。
机制
布局用的是字形的 advance width、行盒的 ascent/descent/line gap,不是字体文件的名字。回退通常是系统西文或中易宋类,和品牌网页字体的字宽表几乎不会一致。同一串字符在两套字下的总宽度不同,就会换行点移动、块级高度跳变。size-adjust、ascent-override、descent-override、line-gap-override 把网页字体的度量调去贴合回退,或反过来把回退调去贴合网页字体,目的是让两次布局的行盒接近,交换近似一次重绘。
没有这些覆盖时,换字等于换了一套约束。CLS 会把这次文本块位移算进去。闪烁策略只决定用户能不能看见交换;看见与否都不取消度量差。策略选了 optional 且未换字,度量差不会发作;一旦发生 swap,度量差就是重排的直接原因。
怎么研究
在实验室记录换字前后同一文本节点的行盒和溢出:用两套字体分别渲染同一字符串,比较宽、行数、块高,再打开 size-adjust 看差值是否收束。现场用 layout-shift 的 source 指向文本节点、且时间落在 FontFace 变为 loaded 附近,来归因“这次 CLS 来自换字”。实验室常命中缓存字体,现场首访才有 swap,二者会 metric mismatch——实验室 CLS 低、现场文本块在跳。不要用实验室的“字体已缓存”会话代表首访。
边界
等宽数字、图标字体之外的纯图标(走 SVG)没有这套西文/中文字宽表问题。用户把默认字体改成与网页字体度量接近的系统字体时,差会缩小。font-display: optional 且首次未命中时不发生第二次布局,度量差潜伏着不表现。垂直书写、竖排的度量轴不同,横向 size-adjust 不能直接搬。打印若只排最终字体,不经过回退交换。
怎么落地
- 为每条
@font-face选定回退栈,用同一段关键文案量两套字体的宽与行高,写入size-adjust和 ascent/descent 覆盖,直到行数不再因换字增减。 - 标题、按钮等不能换行的短文本,除覆盖度量外还要给容器留出两套字体中较宽的一方。
- 中西混排时分别覆盖拉丁与汉字的回退,不要假设一套
size-adjust能同时贴合。 - 验证:冷加载录屏看换字那一帧正文块的底边是否移动;
layout-shift归因到文本节点的条目应随覆盖生效而消失。