不可见文本与换字闪烁是同一取舍的两端
别名: FOIT · FOUT · font-display · 字体闪烁 · flash of invisible text
概念解释
自定义字体还在路上时,浏览器对已有文本只有两种合法画面:把字形画成透明,等目标字体到了再显现;或立刻用回退字体画出来,到了再换成目标字体。前者是不可见文本闪烁(FOIT, flash of invisible text),后者是换字闪烁(FOUT, flash of unstyled text)。font-display 的 block / swap / optional 只是在这条轴上选点,并加上超时。不存在第三种默认路径,能让文字既立刻可读、又绝不换一次字形——除非字体已经在缓存里。
它处理的是等待期间「空」与「闪」这一对代价,不是加载策略会选出哪一种闪烁形式,也不是回退字体和目标字体度量不一致造成的重排。
机制
文本节点在字体未就绪时仍要参与绘制。引擎要么不画前景(可见性为零,占位可以还在),要么用回退字体画。block 把 FOIT 窗口拉长,换字机会变小,但这段时间字是看不见的,阅读和点击目标都空着。swap 立刻 FOUT,可读,随后字形、字重、字宽一换,视线会被拽去注意「字跳了一下」。optional 给一个极短的 block,超时就永远留在回退,等于用「可能永远不是品牌字体」换「几乎不闪」。轴的两端互斥:压短不可见,换字就更明显;禁止换字,不可见窗口就得拉长或放弃品牌字体。
超时不会创造第三态,只会在两端之间跳。网络慢时 block 的上限一到,仍会掉去 swap 或回退,用户先经历一段空白,再经历一次换字——两头的代价叠在同一次加载上。缓存命中时两端都退化成「没有等待」,不能拿缓存会话证明策略没有闪烁。
边界
系统 UI 字体、用户已安装的字体没有这条等待,取舍不发生。图标字体走另一条失败模式(失败留下字符),不要用 FOIT/FOUT 去描述。可变字体一次下载多种字重,能减少「某个字重还在 FOIT、正文已经 FOUT」的撕裂,但首包仍在这条轴上。preload 缩短等待,不取消轴:只是让选点更常落在「已缓存」。打印和生成 PDF 时字体必须嵌入,没有运行时闪烁。对已经在读的长文,中途 swap 造成的换字比首屏 FOIT 更伤,正文与标题可以选不同的点。
怎么落地
- 先决定这条文本更不能忍受哪一端:导航和按钮优先可读(偏 swap / optional),品牌标题可以偏短 block,但要有超时上限。
- 在
font-display上显式选点,不要依赖浏览器默认;不要同时要求「首字节就见字」和「绝对不换字形」却不提供缓存命中。 - 关键字体
preload,把等待从「必然闪一次」压到「多数会话不闪」;仍按未命中缓存来验收。 - 验证:清缓存后用慢网加载,逐帧看等待期是空白还是回退字形、换字发生在第几秒。再对比缓存命中。把两种录像叠在一起,确认所选的点与产品更能忍受的那一端一致,而不是两端都出现。