字体加载太慢怎么优化?
我在项目里用了一个自定义的中文字体,但页面首次加载时明显卡顿,文字会先显示默认字体再闪成自定义字体,用户体验很差。我试过用 font-display: swap,但好像没完全解决问题。
现在想用 JavaScript 监听字体加载完成后再显示内容,但不确定写法对不对,下面这段代码在部分浏览器里根本不触发回调:
const font = new FontFace('MyCustomFont', 'url(./fonts/custom.woff2)');
font.load().then(() => {
document.fonts.add(font);
document.body.classList.add('font-loaded');
}).catch(err => {
console.warn('字体加载失败', err);
});
先说你这段代码的问题。FontFace API本身没写错,但有几个坑:
第一个坑是兼容性。document.fonts 这个API在Safari上支持得比较晚,iOS 10以前根本不支持。你说的"部分浏览器不触发回调",大概率就是兼容性问题。
第二个坑是路径问题。url(./fonts/custom.woff2) 这个相对路径,在某些构建工具打包后可能会出问题,建议用绝对路径或者让构建工具处理。
咱们分几步来彻底解决这个问题:
第一步,最简单有效的方案是用CSS preload预加载字体。在HTML的head里加上:
这样做的好处是浏览器会提前下载字体,不用等到CSS解析到font-face才去下载。注意crossorigin属性必须加,不然预加载会失效,这个坑我踩过。
第二步,你的font-display: swap其实用对了,但它就是会闪烁,这是它的设计初衷——先显示文字,字体加载完再替换。如果你不想闪烁,可以改用font-display: block,但这样文字会延迟显示,用户体验也不一定好。
更推荐的做法是用font-display: optional。这个属性很聪明,如果字体在很短时间内(通常是100ms)加载完了就用自定义字体,没加载完就用系统字体,而且不会出现闪烁。当然缺点就是用户可能看不到自定义字体,但至少体验流畅。
第三步,如果你坚持要用JS监听字体加载,我来给你一个更稳妥的写法:
这里有个关键点:document.fonts.ready 会在所有CSS字体都加载完成后触发,比单独监听某个字体更靠谱。
第四步,配合CSS来做初始隐藏。页面一开始先把内容隐藏,字体加载完再显示:
但是!这里有个问题,如果字体加载失败或者JS报错,用户就看不到内容了。所以一定要加个超时处理:
第五步,也是最关键的一步,针对中文字体的优化:字体子集化。
中文字体几MB甚至十几MB,全部加载肯定慢。你只需要把页面实际用到的字提取出来,生成一个子集字体。用font-spider或者fontmin这类工具:
或者在构建流程里用font-spider,它会自动扫描HTML里用到的所有汉字,生成精简版字体。我之前把一个8MB的中文字体子集化后只有30KB,加载速度直接起飞。
第六步,考虑用woff2格式。你已经在用了,这点很好。woff2比woff压缩率更高,现代浏览器都支持。但记得要保留woff作为降级方案:
总结一下,推荐的完整方案是:preload预加载 + 字体子集化 + font-display: optional + JS超时降级。这套组合拳下来,基本能解决你的问题。
对了,还有个坑要注意:如果你用Webpack或者Vite,记得配置字体文件的hash命名和缓存策略,不然用户每次访问都要重新下载字体。配置好强缓存后,第二次访问就会从本地读取,体验会好很多。