如何优化 HTML 的字体加载:font-display 和 preload?但 preload 已有,字体相关可能新。
优化字体加载需同时配置 preload 与 font-display:preload 控制下载时机,font-display 控制加载完成前的显示;正文用 optional/swap、标题装饰用 fallback、图标用 block,首屏仅
字体加载优化的结论一句话:**用 <link rel="preload"> 决定「字体什么时候开始下载」,用 font-display 决定「字体没下载完时页面显示什么」**,两者职责不重叠,必须同时配。默认不做任何处理时,浏览器要等 CSS 解析完、DOM 构建碰到用字体的文本了才去请求字体,这中间 300ms 到 3s 的空白就是经典的 FOIT(文字不可见)问题。
font-display 的 5 个值分别是什么意思,该怎么选?
font-display 是写在 @font-face 里的一个描述符,它把字体加载过程切成「阻塞期 + 交换期」两段:阻塞期内文字不可见(隐身),交换期内先用兜底字体显示、字体到了再换。
auto:等于浏览器默认,Chrome/Firefox 实际行为接近block,最不可控,别用。block:阻塞期 3s + 无限交换期。3 秒内一片空白,LCP 直接崩,只适合图标字体这类不能出现错误字形的场景。swap:阻塞期 0s + 无限交换期。文字立刻用系统字体显示,字体到了再替换。绝大多数正文字体的默认选择。fallback:阻塞期 100ms + 交换期 3s。3 秒还没到就彻底放弃,本次访问不再换字体。适合「换了会很难看」的装饰性字体。optional:阻塞期 100ms + 交换期 0s。100ms 内没下载完,本页就不换字体了(下载会被降级甚至放弃),下次访问命中缓存才用上。它对 LCP 和 CLS 最友好,适合非品牌强制的正文。
结论:**正文用 optional 或 swap,标题/装饰字体用 fallback,图标字体用 block,永远不要用 auto。**
preload 字体为什么必须写 crossorigin?
因为字体请求按规范一律走 CORS 模式,即使是同域字体也一样。<link rel="preload"> 如果没加 crossorigin,浏览器会认为它预加载的是另一个资源,结果就是字体被下载两次——一次是 preload 的无 CORS 版本(拿到后丢弃),一次是 CSS 里的 CORS 版本。
正确写法(放在 <head> 最前面,早于引用字体的 CSS):
<link rel="preload" href="/fonts/inter-var.woff2"
as="font" type="font/woff2" crossorigin>
三个属性都有用:as="font" 让浏览器用正确的优先级和缓存分区、type="font/woff2" 让不支持 WOFF2 的浏览器直接跳过这次预加载、crossorigin 避免双下载。
结论:preload 字体的 crossorigin 是必填项,漏写会白下载一遍。
preload 加多少条才合适?
结论:首屏只 preload 1-2 个字体文件,多了反而拖慢。
每个 preload 都会占用带宽和连接,字体文件 WOFF2 压缩后单个通常 20-50KB,中文字体即使做了子集化也可能 100KB 以上。如果 preload 了 4 个字体文件,它们会和 CSS、LCP 图片抢同一个 HTTP/2 连接的带宽。
判断标准很简单:只 preload「首屏可视区第一屏文字真正用到、且文件名在 HTML 里能确定」的字体。斜体、粗体、第二屏才出现的字体交给 CSS 按需加载。另外 preload 后如果 3 秒内没用上,Chrome 控制台会报 "The resource was preloaded using link preload but not used within a few seconds",看到这条就说明 preload 多了或路径写错了。
字体替换引起的 CLS 怎么治?
结论:**用 size-adjust + ascent-override 把兜底字体的字形尺寸对齐到 Web 字体,CLS 可以压到 0.01 以内。**
做法是给系统兜底字体也写一个 @font-face,把度量覆盖成 Web 字体的值:
@font-face {
font-family: "Inter Fallback";
src: local("Arial");
size-adjust: 107%;
ascent-override: 90%;
descent-override: 22.43%;
line-gap-override: 0%;
}
body { font-family: "Inter", "Inter Fallback", sans-serif; }
size-adjust 缩放字形整体大小,三个 override 用百分比覆盖上行高、下行高、行间距。具体数值可以用 Malte Ubl 的 fontaine 或 Next.js 内置的 next/font 自动算,不用手推。这三个属性 Chrome 92+、Firefox 89+、Safari 17+ 支持。
还有哪些容易踩的坑?
- 别用第三方 CDN 加载字体:多一次 DNS + TLS 握手,通常 100-300ms。自托管并配
Cache-Control: public, max-age=31536000, immutable。 - 用 Google Fonts 时在 URL 加
display=swap:https://fonts.googleapis.com/css2?family=Inter&display=swap,并对fonts.gstatic.com做preconnect。 - 中文字体务必子集化:用
unicode-range把全量字体拆成几十个分片,只下载页面用到的字符区间,能把 3MB 的字体压到几十 KB。 - 只用 WOFF2:比 WOFF 小约 30%,现代浏览器支持率超过 97%,不需要再挂 WOFF/TTF 兜底。
总结一下:preload 提前发起请求解决「下载晚」,font-display: swap/optional 解决「下载慢时白屏」,size-adjust 系列解决「字体替换时跳版」,三者各管一段,缺一不可;首屏只 preload 1-2 个 WOFF2,crossorigin 不能漏。
转载请注明出处,版权归原作者所有。