CSS如何在Canvas绘图前确保字体样式已引入:利用FontFaceSet API监听加载状态
在前端开发中,使用Canvas绘制自定义文本时,经常会遇到一个令人头疼的问题:明明CSS中已经引入了自定义字体,但在Canvas中绘制出来的文本却依然是默认的回退字体(如宋体或Times New Roman)。这是因为Canvas的绘制是即时性的,如果字体文件还未下载完毕,浏览器就会使用备用字体进行渲染,且之后不会自动重绘。
为了解决这个问题,我们需要在Canvas绘图前确保字体样式已经完全引入并加载完毕。本文将详细介绍如何利用现代浏览器提供的 FontFaceSet API 来精准监听字体的加载状态,从而保证Canvas绘制的完美呈现。
一、 为什么Canvas会出现字体渲染问题?
在普通的DOM元素中,如果自定义字体未加载完成,浏览器会先使用备用字体显示文本,待字体加载完成后自动触发“字体闪烁(FOIT)”或重排重绘,文本会自动替换为新字体。然而,Canvas是基于位图的绘制,一旦 fillText() 执行,文本就被光栅化成了像素。如果此时字体未就绪,绘制出的就是错误字体的像素,即便后续字体加载成功,Canvas上的画面也不会自动更新。
二、 传统方案及其局限性
早期开发者通常采用以下两种方式来解决此问题:
延时绘制(setTimeout):设置一个较长的延时(如2秒)后再绘制Canvas。这种方式极不可靠,网络慢时字体依然未加载,网络快时则白白浪费用户时间。
隐藏DOM占位:在页面中放置一个使用该字体的隐藏元素,借此触发浏览器加载字体。但这依然无法精确判断字体何时加载完成,且增加了无意义的DOM节点。
三、 现代解决方案:FontFaceSet API
现代浏览器提供了 document.fonts 对象,即 FontFaceSet API,它允许开发者直接监听和查询字体文件的加载状态。它提供了几个极其有用的方法:
document.fonts.ready:返回一个Promise,当所有已声明的字体加载完成(或失败)时触发。document.fonts.load(font, text):主动触发指定字体的加载,并返回一个Promise,当该特定字体加载完成时触发。document.fonts.check(font, text):同步检查指定的字体是否已经可用。
四、 实战代码演示
下面我们通过一个完整的示例,展示如何在Canvas绘图前利用FontFaceSet API确保自定义字体已加载。
首先,我们在CSS中引入自定义字体:
@font-face {
font-family: 'MyCustomFont';
/* 请注意将字体文件地址替换为实际地址,这里使用替换后的demo地址 */
src: url('https://www.ipipp.com/fonts/custom-font.woff2') format('woff2'),
url('https://www.ipipp.com/fonts/custom-font.woff') format('woff');
font-weight: normal;
font-style: normal;
font-display: swap; /* 推荐使用swap,优化DOM文本渲染 */
}接着是HTML结构:
<canvas id="myCanvas" width="600" height="200"></canvas>
最后是核心的JavaScript逻辑:
const canvas = document.getElementById('myCanvas');
const ctx = canvas.getContext('2d');
// 方式一:等待页面所有字体加载完成(适用于页面字体种类不多的情况)
document.fonts.ready.then(() => {
drawCanvasText();
});
// 方式二:精确监听特定字体的加载(推荐,更加稳健)
async function drawWithSpecificFont() {
try {
// 主动请求加载 30px 大小的 MyCustomFont
// 即使CSS中没有实际使用该字体的DOM元素,也会触发网络请求
await document.fonts.load('30px MyCustomFont');
// 双重检查:确认字体确实可用
if (document.fonts.check('30px MyCustomFont')) {
drawCanvasText();
} else {
console.warn('字体加载失败,使用备用字体绘制');
drawFallbackText();
}
} catch (e) {
console.error('字体加载异常:', e);
drawFallbackText();
}
}
// 执行绘制
drawWithSpecificFont();
function drawCanvasText() {
// 清空画布(如果之前有绘制过备用字体)
ctx.clearRect(0, 0, canvas.width, canvas.height);
// 设置自定义字体并绘制
ctx.font = '30px MyCustomFont';
ctx.fillStyle = '#333333';
ctx.textBaseline = 'top';
ctx.fillText('Hello Canvas with Custom Font!', 20, 50);
}
function drawFallbackText() {
ctx.font = '30px serif';
ctx.fillStyle = '#999999';
ctx.textBaseline = 'top';
ctx.fillText('Loading Custom Font Failed', 20, 50);
}五、 核心逻辑解析
主动触发加载:如果Canvas是动态生成的,或者页面DOM中并没有使用该自定义字体,浏览器默认可能不会下载该字体文件。使用
document.fonts.load('30px MyCustomFont')可以强制浏览器发起字体资源的网络请求。异步等待:使用
async/await暂停代码执行,直到Promise resolve,这就确保了后续执行fillText时,字体数据已经存在于本地缓存中。容错处理:网络请求随时可能失败,通过
try...catch和document.fonts.check()进行双重保险,可以在字体加载失败时提供降级方案,避免Canvas一片空白。
六、 总结
在Canvas中使用自定义字体,核心痛点在于渲染的即时性与字体加载的异步性之间的矛盾。借助 FontFaceSet API,我们可以彻底抛弃不可靠的 setTimeout,以声明式、异步等待的方式精准掌控字体的加载时机。这不仅提升了代码的健壮性,也保障了视觉呈现的完美无缺。在未来的Web绘图开发中,document.fonts 应当成为处理Canvas字体问题的标准实践。