各位靓仔靓女,晚上好!我是今晚的字体加载问题专家(之一,毕竟专家太多了)。今天咱们聊聊前端字体加载那些事儿,尤其是 JS Font Loading API
,帮你告别 FOUT 和 FOIT 的烦恼。保证让你的网页字体加载又快又稳,逼格瞬间提升!
开场白:字体,网页的颜值担当
话说,咱们做前端的,谁不想让自己的页面美美的?字体,绝对是提升颜值的关键因素。想想那些设计精美的网站,哪个不是在字体上下足了功夫?
但是!理想很丰满,现实很骨感。自定义字体用起来爽,加载起来却可能让你吐血。最常见的就是 FOUT (Flash of Unstyled Text) 和 FOIT (Flash of Invisible Text)。
- FOUT: 先显示默认字体,然后突然切换成自定义字体,页面“Duang”的一下,丑爆了。
- FOIT: 页面先啥也不显示,等自定义字体加载完才出现,用户体验极差。
这俩货就像网页界的“牛皮癣”,影响美观不说,还影响用户体验。还好,咱们有 Font Loading API
这把利剑,可以斩妖除魔,让字体加载变得可控。
第一部分:认识 Font Loading API
Font Loading API
是一组 JS API,允许你:
- 检测字体是否加载完成。
- 监听字体加载事件。
- 控制字体加载行为。
简单来说,就是让你可以更精确地控制字体加载的过程,从而避免 FOUT 和 FOIT。
核心接口:FontFace
和 document.fonts
Font Loading API
的核心是 FontFace
接口和 document.fonts
对象。
-
FontFace
接口: 代表一个自定义字体。你可以用它来定义字体的font-family
、src
、font-weight
等属性。 -
document.fonts
对象: 代表当前文档中所有已加载和正在加载的字体集合。它提供了一些方法来管理字体,比如添加、删除、检测字体等。
举个栗子:创建并加载一个字体
// 1. 创建 FontFace 对象
const myFont = new FontFace('MyCustomFont', 'url(./fonts/MyCustomFont.woff2)');
// 2. 加载字体
myFont.load().then(function() {
// 3. 将字体添加到 document.fonts
document.fonts.add(myFont);
// 4. 应用字体
document.body.style.fontFamily = 'MyCustomFont, sans-serif';
console.log('字体加载完成!');
}).catch(function(error) {
console.error('字体加载失败:', error);
});
这个例子做了以下几件事:
- 创建了一个名为
MyCustomFont
的FontFace
对象,并指定了字体文件的 URL。 - 调用
myFont.load()
方法开始加载字体。这个方法返回一个 Promise,可以在加载成功或失败时执行相应的操作。 - 如果字体加载成功,就调用
document.fonts.add(myFont)
将字体添加到文档中。 - 最后,将
document.body
的fontFamily
设置为MyCustomFont
,这样整个页面的字体就变成了自定义字体。
第二部分:实战:优雅地加载字体,告别 FOUT 和 FOIT
现在,咱们来点实际的,看看如何利用 Font Loading API
解决 FOUT 和 FOIT 问题。
策略一:先隐藏,后显示(FOIT 的变种,可控的 FOIT)
这种策略的核心是,在字体加载完成之前,先隐藏页面内容或者只显示占位符,等字体加载完成后再显示真实内容。
<!DOCTYPE html>
<html>
<head>
<title>Font Loading API Example</title>
<style>
body {
font-family: sans-serif; /* 默认字体 */
opacity: 0; /* 初始隐藏 */
transition: opacity 0.5s ease-in-out; /* 添加过渡效果 */
}
body.fonts-loaded {
opacity: 1; /* 字体加载完成后显示 */
}
</style>
</head>
<body>
<h1>Hello, World!</h1>
<p>This is a paragraph of text.</p>
<script>
const myFont = new FontFace('MyCustomFont', 'url(./fonts/MyCustomFont.woff2)');
myFont.load().then(function() {
document.fonts.add(myFont);
document.body.style.fontFamily = 'MyCustomFont, sans-serif';
document.body.classList.add('fonts-loaded'); // 添加类名,显示内容
}).catch(function(error) {
console.error('字体加载失败:', error);
document.body.classList.add('fonts-loaded'); // 即使加载失败也显示,防止页面一直空白
});
</script>
</body>
</html>
这个例子的关键点:
body
初始设置opacity: 0;
让页面内容初始隐藏。transition: opacity 0.5s ease-in-out;
添加过渡效果,让显示过程更平滑。body.fonts-loaded
类名用于控制opacity
的值,当字体加载完成后,添加这个类名,让页面内容显示出来。- 加载失败处理: 即使字体加载失败,也要添加
fonts-loaded
类名,防止页面一直空白。
优点: 简单粗暴,兼容性好。
缺点: 用户可能会看到一段时间的空白,体验稍差。
策略二:使用 font-display
(CSS)
font-display
是一个 CSS 属性,用于控制字体在加载过程中的显示行为。它有几个可选值:
auto
: 浏览器自行决定,通常和block
行为类似。block
: 先隐藏文本,直到字体加载完成。 (FOIT)swap
: 先显示默认字体,字体加载完成后切换为自定义字体。(FOUT)fallback
: 先显示默认字体,一段时间后如果字体还没有加载完成,则继续显示默认字体。optional
: 浏览器自行决定,如果字体加载速度慢,则不使用自定义字体。
@font-face {
font-family: 'MyCustomFont';
src: url('./fonts/MyCustomFont.woff2') format('woff2');
font-weight: normal;
font-style: normal;
font-display: swap; /* 或者 fallback */
}
body {
font-family: 'MyCustomFont', sans-serif;
}
这个例子的关键点:
font-display: swap;
告诉浏览器先显示默认字体,字体加载完成后再切换为自定义字体,可以避免 FOIT。font-display: fallback;
则更加灵活,先显示默认字体,一段时间后如果字体还没有加载完成,则继续显示默认字体,避免长时间的 FOUT。
优点: 简单易用,只需要修改 CSS。
缺点: 无法精确控制字体加载过程,可能会出现短暂的 FOUT。
策略三:结合 Font Loading API 和 CSS 类名
这种策略结合了 Font Loading API
的精确控制和 CSS 类名的灵活性,可以实现更优雅的字体加载效果。
<!DOCTYPE html>
<html>
<head>
<title>Font Loading API Example</title>
<style>
body {
font-family: sans-serif; /* 默认字体 */
}
body.fonts-loading {
/* 可以添加加载中的样式,比如 loading 图标 */
}
body.fonts-loaded {
font-family: 'MyCustomFont', sans-serif;
}
@font-face {
font-family: 'MyCustomFont';
src: url('./fonts/MyCustomFont.woff2') format('woff2');
font-weight: normal;
font-style: normal;
font-display: swap;
}
</style>
</head>
<body>
<h1>Hello, World!</h1>
<p>This is a paragraph of text.</p>
<script>
document.body.classList.add('fonts-loading'); // 添加 loading 类名
const myFont = new FontFace('MyCustomFont', 'url(./fonts/MyCustomFont.woff2)');
myFont.load().then(function() {
document.fonts.add(myFont);
document.body.classList.remove('fonts-loading'); // 移除 loading 类名
document.body.classList.add('fonts-loaded'); // 添加 loaded 类名
}).catch(function(error) {
console.error('字体加载失败:', error);
document.body.classList.remove('fonts-loading'); // 移除 loading 类名
document.body.classList.add('fonts-loaded'); // 即使加载失败也添加 loaded 类名,使用默认字体
});
</script>
</body>
</html>
这个例子的关键点:
body.fonts-loading
类名用于在字体加载过程中添加加载中的样式,比如显示 loading 图标。body.fonts-loaded
类名用于在字体加载完成后应用自定义字体。font-display: swap;
在@font-face
规则中设置font-display
,可以进一步优化字体加载体验。
优点: 可以精确控制字体加载过程,并添加自定义的加载样式。
缺点: 代码稍微复杂一些。
第三部分:高级技巧和注意事项
- 字体预加载: 使用
<link rel="preload">
标签可以提前加载字体,提高加载速度。
<link rel="preload" href="./fonts/MyCustomFont.woff2" as="font" type="font/woff2" crossorigin>
- 字体格式优化: 选择合适的字体格式,比如 WOFF2 格式,可以减小字体文件的大小,提高加载速度。
- 字体子集化: 只包含页面中用到的字符,可以显著减小字体文件的大小。可以使用工具,比如
Font subsetter
。 - 缓存: 合理配置 HTTP 缓存,可以避免重复加载字体文件。
- 错误处理: 一定要处理字体加载失败的情况,避免页面出现错误。
- 跨域问题: 如果字体文件和页面不在同一个域名下,需要配置 CORS,允许跨域访问。 在服务器端设置
Access-Control-Allow-Origin
。 document.fonts.ready
: 使用document.fonts.ready
Promise 来确保所有字体都已加载完成。这个 Promise 在所有字体加载完毕后 resolve。
document.fonts.ready.then(() => {
console.log('所有字体加载完成!');
document.body.classList.add('fonts-loaded');
});
表格总结:各种策略对比
策略 | 优点 | 缺点 | 适用场景 |
---|---|---|---|
先隐藏,后显示 | 简单粗暴,兼容性好 | 用户可能会看到一段时间的空白,体验稍差 | 对体验要求不高,希望避免 FOUT 的情况 |
font-display (CSS) |
简单易用,只需要修改 CSS | 无法精确控制字体加载过程,可能会出现短暂的 FOUT | 对体验有一定要求,允许短暂的 FOUT,但不希望出现 FOIT 的情况 |
结合 Font Loading API 和 CSS 类名 | 可以精确控制字体加载过程,并添加自定义的加载样式 | 代码稍微复杂一些 | 对体验要求高,希望精确控制字体加载过程,并添加自定义加载样式的情况 |
第四部分:常见问题解答
-
Q:
FontFace
对象可以重复使用吗?
A:不可以。每个FontFace
对象只能使用一次。如果你需要在多个地方使用同一个字体,需要创建多个FontFace
对象。 -
Q:
document.fonts.add()
方法可以重复调用吗?
A:可以,但是没有意义。重复调用只会添加一次字体。 -
Q:如何判断字体是否已经加载完成?
A:可以使用document.fonts.check()
方法或者监听document.fonts.onloadingdone
事件。 -
Q:
document.fonts.onloadingdone
、document.fonts.onloadingerror
、document.fonts.onloading
有什么区别?
A:onloadingdone
:所有字体加载完成时触发。onloadingerror
:字体加载失败时触发。onloading
:字体开始加载时触发。
第五部分:结束语
好了,今天的分享就到这里。希望大家通过今天的学习,能够掌握 Font Loading API
的使用方法,告别 FOUT 和 FOIT 的烦恼,让你的网页字体加载又快又稳,颜值爆表!记住,字体虽小,细节决定成败。
最后,给大家留个小作业:尝试使用 Font Loading API
和 CSS 类名,实现一个带有加载动画的字体加载效果。期待你们的优秀作品!
散会!