2、字体加载事件:@font-face规则详解、font-display属性、字体加载的生命周期
字体加载,说白了就是浏览器去服务器「取」字体文件的过程。这个过程不像你想的那么简单——它涉及网络请求、解析、渲染,还有一堆你可能没注意到的细节。我刚开始做前端时,就因为在字体加载上栽过跟头,页面白屏了好几天才找到原因。
这一节,我们就把 @font-face 规则、font-display 属性,以及字体加载的完整生命周期讲透。你掌握了这些,以后遇到字体加载问题,心里就有底了。
2.1 @font-face 规则详解
@font-face 是 CSS 中定义自定义字体的核心规则。它的作用,就是告诉浏览器:「嘿,我这里有个字体文件,你拿去用吧。」
基本语法长这样:
@font-face {
font-family: 'MyCustomFont';
src: url('fonts/myfont.woff2') format('woff2'),
url('fonts/myfont.woff') format('woff');
font-weight: normal;
font-style: normal;
font-display: swap;
}
这里面有几个关键属性,我一个个说。
2.1.1 font-family:给字体起个名字
这个属性定义字体的「别名」。你在页面其他地方用 font-family: 'MyCustomFont' 时,浏览器就知道要加载这个字体文件了。
命名时注意:
- 名字可以包含空格,但要用引号括起来
- 避免和系统字体重名,比如别叫 'Arial' 或 'Helvetica'
- 我习惯用项目名+字体名的方式,比如 'ProjectName-Regular'
2.1.2 src:告诉浏览器字体文件在哪
src 属性指定字体文件的路径和格式。这里有个优先级问题:浏览器会按顺序尝试加载,第一个能用的就用它。
我建议的写法:
src: url('fonts/myfont.woff2') format('woff2'),
url('fonts/myfont.woff') format('woff'),
url('fonts/myfont.ttf') format('truetype');
为什么 woff2 放前面?因为它的压缩率最高,体积最小。浏览器支持 woff2 就用它,不支持就降级到 woff,再不行就用 ttf。我在项目中遇到过,有些老旧浏览器只认 ttf,所以保留一个兜底格式很重要。
2.1.3 font-weight 和 font-style:定义字体的「变体」
这两个属性告诉浏览器,这个字体文件对应的是哪个字重和样式。比如:
@font-face {
font-family: 'MyFont';
src: url('myfont-bold.woff2') format('woff2');
font-weight: 700;
font-style: normal;
}
@font-face {
font-family: 'MyFont';
src: url('myfont-italic.woff2') format('woff2');
font-weight: 400;
font-style: italic;
}
这样,你在 CSS 里写 font-weight: 700 时,浏览器就会加载粗体版本。写 font-style: italic 时,加载斜体版本。每个变体都需要单独定义 @font-face。
2.2 font-display 属性:控制字体加载时的「等待策略」
font-display 是 @font-face 里的一个属性,但它太重要了,值得单独拿出来讲。它决定了浏览器在字体加载完成之前,怎么处理页面上的文字。
说白了,就是三个选择:
- 等字体加载完再显示文字(FOIT:Flash of Invisible Text)
- 先用系统字体显示,加载完再替换(FOUT:Flash of Unstyled Text)
- 等一会儿,加载不完就用系统字体(折中方案)
font-display 有五个可选值:
| 值 | 行为描述 | 适用场景 |
|---|---|---|
auto |
浏览器默认行为,通常是 FOIT,等待时间很短 | 不关心加载策略时 |
block |
先隐藏文字,等字体加载完再显示(无限等待) | 图标字体、品牌字体,必须用自定义字体 |
swap |
先用系统字体显示,加载完立即替换 | 正文内容,用户体验优先 |
fallback |
先隐藏约 100ms,然后用系统字体显示;如果 3 秒内加载完就替换 | 折中方案,兼顾品牌和体验 |
optional |
先隐藏约 100ms,然后用系统字体显示;如果加载慢就不替换了 | 网络差时,避免字体闪烁 |
我个人习惯:正文用 swap,图标字体用 block,品牌标题用 fallback。你想想看,用户打开你的页面,如果正文文字一直空白,他肯定以为页面坏了。先用系统字体显示,等自定义字体加载完再替换,体验就好很多。
2.3 字体加载的生命周期
字体加载不是一瞬间完成的。它有一个完整的状态机,包含四个阶段:pending、loading、loaded、error。
我用一张图来展示这个流程:
2.3.1 Pending(待加载)
这是字体加载的起点。浏览器解析到 @font-face 规则后,会创建一个字体对象,但还没发起网络请求。此时字体处于「待命」状态。
触发条件:
- 页面中某个元素使用了这个字体
- 或者 CSS 中引用了这个字体
如果没有任何元素使用这个字体,浏览器不会发起加载。嗯,这里要注意:你定义了 @font-face 但没用它,浏览器是不会下载字体文件的。
2.3.2 Loading(加载中)
浏览器开始下载字体文件。这个阶段,网络请求已经发出,字体数据正在传输。
这个阶段的特点:
- 浏览器会检查字体文件的格式是否合法
- 如果字体文件很大,加载时间会很长
- font-display 属性在这个阶段开始生效
我在项目中遇到过,一个中文字体文件动辄几 MB,加载时间能到好几秒。这时候如果 font-display 设置不当,页面就会长时间空白。
2.3.3 Loaded(加载完成)
字体文件下载完成,浏览器解析成功。此时页面上的文字会从系统字体切换到自定义字体。
这个阶段会触发一个事件:document.fonts.ready。你可以用 JavaScript 监听这个事件:
document.fonts.ready.then(function() {
console.log('所有字体加载完成!');
// 可以在这里做一些后续操作,比如隐藏加载提示
});
或者监听单个字体的加载状态:
var font = new FontFace('MyFont', 'url(myfont.woff2)');
font.load().then(function() {
console.log('MyFont 加载成功');
document.fonts.add(font);
}).catch(function() {
console.log('MyFont 加载失败');
});
2.3.4 Error(加载失败)
字体加载失败的原因有很多:
- 网络问题:服务器挂了、网络断了
- 文件问题:字体文件损坏、格式不对
- CORS 问题:跨域请求没有设置正确的响应头
- 路径问题:URL 写错了
加载失败后,浏览器会使用 fallback 字体(你指定的后备字体或系统默认字体)。页面不会崩溃,但视觉效果会打折扣。
2.4 实战:监听字体加载事件
说了这么多理论,我们来点实战。怎么在代码里监听字体的加载状态?
方法一:使用 CSS Font Loading API
// 检查字体是否已加载
if (document.fonts.check('12px MyFont')) {
console.log('字体已加载');
} else {
console.log('字体尚未加载');
}
// 监听所有字体加载完成
document.fonts.ready.then(function() {
console.log('所有字体就绪');
});
方法二:使用 FontFace 对象
// 手动创建并加载字体
var myFont = new FontFace('MyFont', 'url(myfont.woff2)');
myFont.load().then(function(loadedFont) {
// 加载成功,添加到文档
document.fonts.add(loadedFont);
document.body.style.fontFamily = 'MyFont, sans-serif';
}).catch(function(error) {
// 加载失败,使用后备字体
console.error('字体加载失败:', error);
document.body.style.fontFamily = 'sans-serif';
});
好了,关于字体加载事件的核心内容就这些。你掌握了 @font-face 的配置、font-display 的策略选择,以及字体加载的四个生命周期阶段,以后遇到字体加载问题,就能从容应对了。