字体预加载实战:提升网页加载性能的关键技巧
优化前:卡得不行
上周上线一个新页面,字体用的是自定义的 Inter 和一个中文思源黑体。本地开发跑着挺顺,但一上测试环境,用户反馈“白屏好几秒”“字突然蹦出来吓一跳”。我自己打开 Lighthouse 一看,FCP(First Contentful Paint)直接飙到 4.8s,LCP 更是干到 5.2s,字体加载成了性能瓶颈。
最烦的是 FOIT(Flash of Invisible Text)——文字区域完全空白,等字体下载完才唰一下显示。用户以为页面没加载完,狂点刷新,结果体验极差。说实话,我一开始真没把字体当回事,以为 CDN 缓存一下就完了,结果现实狠狠打脸。
找到瓶颈了!
我先用 Chrome DevTools 的 Network 面板看了下资源加载顺序。发现关键问题:HTML 渲染完后,浏览器才开始解析 CSS,然后才发现要加载字体,再发起请求。这中间至少有 1-2 秒的空窗期。
接着跑了个 Performance 录制,果然看到主线程在等待字体资源时啥也没干。Lighthouse 报告也明确提示:“确保文本在网页字体加载期间保持可见”“考虑预加载关键字体资源”。
折腾了半天发现,问题核心就两点:字体加载太晚 + 没有 fallback 策略。前者导致延迟,后者导致白屏。
试了几种方案,最后这个效果最好
我先是尝试了最简单的办法:把 @font-face 放到 HTML 头部的内联 style 里。这样能早点触发字体请求,但实测发现提升有限,因为浏览器还是得先解析 CSS 才知道要加载啥。
然后试了 font-display: swap。这个确实能解决白屏问题——用系统默认字体先显示文字,等自定义字体加载完再替换。但问题是,字体切换时页面会“跳一下”,尤其在中文场景下,行高、字宽差异明显,用户会觉得内容在“抖动”。虽然比白屏好,但体验还是不够稳。
真正起效的是预加载(preload)。直接告诉浏览器:“这个字体很重要,别等 CSS 解析了,现在就去下载!”
具体做法是在 <head> 里加一行:
<link rel="preload" href="/fonts/Inter-Regular.woff2" as="font" type="font/woff2" crossorigin>
注意几个细节:
as="font"必须写,不然浏览器不知道这是字体资源type="font/woff2"建议加上,帮助浏览器判断是否支持crossorigin属性不能少,哪怕字体在同域——这是规范要求,否则预加载会失效
配合 font-display: optional 或 swap 使用效果更佳。我最终选了 optional,意思是“如果字体没在极短时间内加载完,就永远别用了,直接用 fallback 字体”。这对首屏性能最友好。
完整代码对比:
优化前:
@font-face {
font-family: 'Inter';
src: url('/fonts/Inter-Regular.woff2') format('woff2');
font-weight: 400;
font-style: normal;
}
body {
font-family: 'Inter', sans-serif;
}
优化后:
<head>
<link rel="preload" href="/fonts/Inter-Regular.woff2" as="font" type="font/woff2" crossorigin>
<style>
@font-face {
font-family: 'Inter';
src: url('/fonts/Inter-Regular.woff2') format('woff2');
font-weight: 400;
font-style: normal;
font-display: optional;
}
body {
font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
}
</style>
</head>
这里我把 @font-face 内联到 HTML 里了,避免额外 CSS 请求延迟。如果你用构建工具(比如 Webpack),也可以用插件自动注入 preload 标签,但手动控制更精准。
踩坑提醒:我一开始忘了加 crossorigin,结果预加载的字体和 CSS 里声明的被当成两个资源,重复下载!Network 面板里看到两个 Inter-Regular.woff2 请求,差点没气死。查了 MDN 才知道,字体预加载必须带 crossorigin,哪怕同源。
性能数据对比
改完之后重新跑 Lighthouse:
- FCP 从 4.8s → 0.9s
- LCP 从 5.2s → 1.1s
- 字体资源加载时间提前了约 1.3 秒(从 HTML 解析后移到 HTML 开始解析时)
实际手机 4G 网络下测试,页面文字几乎“秒出”,不再有空白等待。即使自定义字体没加载完,fallback 字体也能保证内容可读,用户不会觉得卡。
还有一个意外收获:因为用了 font-display: optional,后续访问如果字体还在缓存中,就直接用;如果网络慢,干脆不用,避免布局偏移。反而提升了交互稳定性。
当然,这方案不是万能的。比如你页面用了很多字体(粗体、斜体、多语言),全 preload 可能适得其反——会阻塞其他关键资源。我的建议是:只 preload 首屏必需的那 1-2 个字体文件,其他的让它们按需加载。
结尾碎碎念
这次优化说复杂也不复杂,核心就三件事:preload 关键字体、设置合理的 font-display、fallback 字体选靠谱的系统字体。但细节特别容易踩坑,尤其是 crossorigin 和资源重复加载的问题。
另外,woff2 格式一定要用,体积比 woff 小 30% 以上。我们项目里 Inter-Regular.woff2 只有 160KB,换成 woff 得 240KB,差距明显。
以上是我踩坑后的总结,亲测有效。如果你有更好的字体加载策略,或者遇到类似问题怎么解的,欢迎评论区交流。这种性能优化真的没有银弹,很多时候就是一点点抠细节堆出来的体验。

暂无评论