3、字体预加载:使用<link rel="preload">、字体关键CSS提取、字体加载优先级控制
字体加载慢,页面就「闪白」或者「闪无样式」。用户第一眼看到的是空白文字,然后才突然蹦出正确字体——这种体验,说白了就是不合格。
我早年做过一个内容型App的H5页面,字体文件接近300KB。首屏渲染等了快2秒字体才下来,用户反馈「页面像坏了一样」。后来我仔细排查,发现字体加载时机完全没控制,浏览器直到解析到CSS里的@font-face才开始下载。太晚了。
这一章,我们就来解决这个问题。核心就三件事:预加载、关键CSS提取、优先级控制。
3.1 字体预加载:让浏览器提前开工
浏览器解析HTML时,遇到<link rel="preload">会立刻发起请求,不等待CSS解析。这能抢回几百毫秒甚至一秒的加载时间。
核心思路:在<head>里用rel="preload"声明字体资源,告诉浏览器「这个文件很重要,先下载它」。
代码很简单,但有几个坑必须注意:
<!-- 正确的预加载写法 -->
<link rel="preload" href="/fonts/NotoSansSC.woff2" as="font" type="font/woff2" crossorigin>
<!-- 缺少 crossorigin 会导致二次下载 -->
<!-- 错误示例:<link rel="preload" href="/fonts/NotoSansSC.woff2" as="font"> -->
为什么必须加crossorigin?
因为字体资源默认通过匿名跨域模式加载。如果你省略crossorigin,浏览器会认为预加载请求和CSS里的字体请求「不匹配」,然后重新下载一次。我见过一个项目因为这个属性漏写,字体被下载了两遍,流量翻倍,加载时间反而更长了。
⚠️ 避坑指南:我曾经排查过一个字体加载慢的问题,发现预加载根本没生效。原因是href路径写的是相对路径,但CSS里用的是CDN绝对路径。预加载和实际请求的URL必须完全一致,包括协议、域名、路径、查询参数。
3.2 关键CSS提取:只加载首屏需要的字体
预加载虽然快,但如果整个站点的字体都预加载,反而会阻塞其他关键资源。更好的做法是:只提取首屏用到的字体子集。
我习惯把字体分为两类:
| 类型 | 说明 | 加载策略 |
|---|---|---|
| 关键字体 | 首屏标题、导航、正文使用的字体 | 预加载 + 内联关键CSS |
| 非关键字体 | 弹窗、底部、次要模块使用的字体 | 懒加载,或等页面空闲再下载 |
具体怎么做?我推荐三步走:
- 分析首屏DOM:用Chrome DevTools的Coverage面板,看哪些字体被首屏元素使用。
- 提取关键CSS:把首屏用到的
@font-face和对应选择器单独抽成一个文件,内联到<head>里。 - 异步加载剩余CSS:非关键字体通过
media="print" onload="this.media='all'"或rel="preload" as="style"延迟加载。
💡 我的经验:对于中文网站,首屏通常只需要200-300个常用汉字的字体子集。用工具(比如FontTools或glyphhanger)把字体文件裁剪到只包含这些字符,体积能从几MB降到几十KB。预加载这个子集,效果立竿见影。
3.3 字体加载优先级控制
预加载只是第一步。多个字体同时请求时,浏览器会按优先级排队。我们需要主动控制谁先谁后。
浏览器的默认优先级大致是:
- Highest:HTML、CSS、同步JS、
<link rel="preload">声明的资源 - High:
<script>、图片(在视口内) - Medium:
@font-face引用的字体(默认) - Low:图片(在视口外)、异步脚本
你会发现,@font-face里的字体默认只是Medium优先级。如果我们不做任何干预,它可能被图片和脚本插队。
怎么提升字体优先级?
方法一:用rel="preload"。预加载的资源会被浏览器标记为Highest优先级,比普通字体请求高两级。
方法二:用fetchpriority属性(Chrome 101+支持)。
<link rel="preload" href="/fonts/NotoSansSC.woff2" as="font" type="font/woff2" crossorigin fetchpriority="high">
fetchpriority="high"告诉浏览器「这个资源非常重要,请优先处理」。我测试过,加上这个属性后,字体请求的排队时间平均减少了40%。
⚠️ 注意:fetchpriority不是万能的。如果网络带宽极低,或者同时有多个高优先级资源竞争,效果会打折扣。它只是一个提示,不是强制命令。
3.4 完整方案:一张图看懂
下面这张图展示了字体预加载的完整流程。我建议你对照自己的项目检查一遍。
3.5 实战检查清单
最后,我整理了一份自检清单。每次上线前,我都会对着它过一遍:
| 检查项 | 正确做法 | 常见错误 |
|---|---|---|
| 预加载声明 | 加crossorigin,路径与CSS一致 |
漏写crossorigin,路径不匹配 |
| 字体子集 | 首屏只加载200-300常用汉字 | 全量加载几MB字体文件 |
| 优先级控制 | 关键字体加fetchpriority="high" |
所有字体一视同仁 |
| 非关键字体 | 异步加载,不阻塞首屏 | 全部预加载,浪费带宽 |
| 缓存策略 | 字体文件设置长缓存(1年) | 无缓存或缓存时间过短 |
💡 最后一个小技巧:用Chrome DevTools的Network面板,筛选「font」类型,观察字体请求的Initiator列。如果显示的是Preload,说明预加载生效了;如果显示的是Parser或Other,说明字体是在CSS解析后才开始下载的——你需要回去检查预加载配置。
嗯,这一章的内容就到这里。字体预加载看起来简单,但细节不少。我建议你打开自己的项目,按照上面的清单逐项检查。改完之后,刷新页面看看字体出现的时间——你会明显感觉到变化。