在 HTML Canvas 中进行文本绘制时,系统默认字体往往只能满足基础展示,很难承载品牌字形、界面标题、数据可视化说明等个性化需求。要让画布中的文字使用自定义字体,需要同时处理两个层面的问题:一是通过 CSS 将字体资源注册到当前文档,二是让 Canvas 绘图上下文在正确的时机读取该字体。由于字体文件通常通过网络获取,加载过程具有异步性,如果在字体尚未就绪时就执行绘制,浏览器可能先用默认字体替代,导致最终画面与设计预期不一致。因此,围绕字体声明、字体调用和加载完成后的重绘建立清晰流程,是保证 Canvas 文本稳定呈现的关键。

Canvas 文本绘制的基础与 font 属性
Canvas 的二维上下文主要提供两个文本绘制方法,分别是 fillText 和 strokeText。前者用于绘制填充颜色的文字,后者用于绘制描边文字。在调用这两个方法之前,通常需要先设置上下文的字体属性。Canvas 中的 ctx.font 属性接受类似 CSS font 简写规则的字符串,可以包含字体大小、字体族,也可以进一步描述粗细、斜体等样式。
在实际书写时,字体族名称是 Canvas 能否找到目标字体的关键。如果只写一个自定义字体名称,建议在其后追加通用字体族作为回退,例如 sans-serif。这样即使自定义字体暂时没有加载成功,或者名称书写存在偏差,文本仍然可以正常显示,不会出现完全无法阅读的情况。绘制文本时还需要配合 fillStyle、strokeStyle、lineWidth 等属性,控制文字的颜色和描边效果。
下面是一个基础文本绘制示例,展示如何先设置字体,再分别绘制填充文本和描边文本。
// 获取画布元素和二维绘图上下文
const canvas = document.getElementById('myCanvas');
const ctx = canvas.getContext('2d');
// 设置字体大小和字体族
ctx.font = '24px sans-serif';
// 设置填充颜色并绘制填充文本
ctx.fillStyle = '#333';
ctx.fillText('默认字体文本', 50, 100);
// 设置描边颜色并绘制描边文本
ctx.strokeStyle = '#ff0000';
ctx.strokeText('描边文本示例', 50, 150);
这段代码使用的是通用字体族,因此通常可以立即看到结果。如果后续要替换成自定义字体,只需要修改 ctx.font 中的字体族名称。但需要注意的是,字体名称能够被 Canvas 识别的前提,是该字体已经通过 CSS 注册到当前文档中。
通过 @font-face 将自定义字体接入文档
Canvas 本身并不负责下载字体文件,它只是使用当前文档中已经可用的字体资源。因此,要让 Canvas 使用自定义字体,首先需要通过 CSS 的 @font-face 规则声明字体。声明时需要指定字体族名称和字体文件地址,浏览器会根据这些信息下载并注册字体。字体族名称之后会成为 CSS 与 Canvas 之间的桥梁,Canvas 通过相同名称调用该字体。
在 @font-face 规则中,font-family 用于定义字体名称,src 用于指定字体文件来源。为了更好的浏览器兼容性,可以同时提供多个字体格式,例如 woff2 和 woff。此外,font-weight 和 font-style 也建议明确声明,避免与同名字体的其他字重或斜体版本混淆。
/* 声明自定义字体 */
@font-face {
font-family: 'MyCustomFont';
src: url('https://ipipp.com/fonts/my-custom-font.woff2') format('woff2'),
url('https://ipipp.com/fonts/my-custom-font.woff') format('woff');
font-weight: normal;
font-style: normal;
}
/* 给画布添加边框,便于观察绘制范围 */
#myCanvas {
border: 1px solid #ccc;
}
字体声明完成后,就可以在 Canvas 的 font 属性中使用相同的字体族名称。如果字体名称中包含空格,可以使用引号包裹。为了保留回退能力,仍然建议在自定义字体后面追加通用字体族。
// 获取画布和上下文
const canvas = document.getElementById('myCanvas');
const ctx = canvas.getContext('2d');
// 使用已经通过 CSS 声明的自定义字体
ctx.font = '24px MyCustomFont, sans-serif';
ctx.fillStyle = '#0066cc';
// 绘制自定义字体文本
ctx.fillText('自定义字体文本', 50, 200);
这里只是完成了字体调用,并不代表字体一定已经下载完毕。如果此时字体文件仍在加载,Canvas 可能暂时使用回退字体渲染文本。因此,下一节需要解决异步加载完成后再绘制的问题。
异步加载完成后再执行绘制
字体文件的加载是异步过程,尤其是在网络较慢或字体文件较大的情况下,页面脚本可能已经执行到绘制逻辑,而字体还没有准备就绪。Canvas 与普通 DOM 文本不同,它不会在字体到达后自动重新绘制已经生成的画布内容。如果第一次绘制时字体不可用,画布上留下的就是回退字体的结果。为了避免这种情况,应当把绘制逻辑放到字体加载完成之后执行。
现代浏览器提供了 document.fonts.ready 接口,它会返回一个 Promise。当文档中已经触发加载的字体进入稳定状态后,这个 Promise 会完成。对于已经在 CSS 中声明并且希望整体等待字体就绪的场景,这种方式比较简单。绘制成功时使用自定义字体,加载失败时则回退到默认字体,可以保证画布内容始终有可读结果。
// 获取画布和上下文
const canvas = document.getElementById('myCanvas');
const ctx = canvas.getContext('2d');
// 等待文档字体加载完成
document.fonts.ready.then(() => {
// 字体就绪后使用自定义字体绘制
ctx.font = '24px MyCustomFont, sans-serif';
ctx.fillStyle = '#333';
ctx.fillText('字体加载完成后绘制的文本', 50, 250);
}).catch(() => {
// 加载失败时使用默认字体兜底
ctx.font = '24px sans-serif';
ctx.fillStyle = '#666';
ctx.fillText('字体加载失败,使用默认字体', 50, 250);
});
如果希望按需加载某一个字体,而不是等待整个文档字体集合,可以使用 FontFace 接口。通过 FontFace 可以在脚本中创建字体对象,将其加入文档字体集合,然后主动调用 load 方法。这种方式适合动态切换主题、根据用户操作加载特殊字体,或者在页面初始化阶段提前加载 Canvas 所需字体。
// 创建自定义字体对象
const customFont = new FontFace('MyCustomFont', 'url(https://ipipp.com/fonts/my-custom-font.woff2)');
// 将字体加入文档字体集合
document.fonts.add(customFont);
// 主动加载字体并在完成后绘制
customFont.load().then(() => {
const canvas = document.getElementById('myCanvas');
const ctx = canvas.getContext('2d');
ctx.font = '24px MyCustomFont, sans-serif';
ctx.fillStyle = '#0066cc';
ctx.fillText('单独加载的自定义字体文本', 50, 300);
}).catch(() => {
console.error('自定义字体加载失败');
});
无论使用哪种方式,都应当为失败情况准备兜底方案。字体加载失败可能来自地址错误、格式不支持、网络异常或跨域限制。此时如果仍然强制使用自定义字体,可能导致文本显示异常。更稳妥的做法是回退到通用字体,并保留必要的日志信息,方便后续排查。
常见问题排查与工程化建议
在实际项目中,Canvas 自定义字体不生效通常不是单一原因造成的。首先应检查字体族名称是否完全一致,包括大小写、空格和引号使用。CSS 中声明的名称与 Canvas 中使用的名称必须匹配。其次要检查字体文件地址是否可以正常访问,字体格式是否被当前浏览器支持。如果字体文件部署在其他域名下,还需要确认服务器是否允许跨域读取字体资源。
另一个常见问题是首帧闪烁或首次绘制使用了默认字体。出现这种情况时,可以考虑提前触发字体加载,而不是等到真正绘制 Canvas 时才开始请求字体。例如,可以在页面初始化阶段使用 FontFace 的 load 方法主动加载字体,也可以让页面中某个不可见区域提前应用该字体。对于需要导出图片的 Canvas 应用,尤其要避免在导出前一刻才加载字体。
如果项目中使用多个字重或多个字体族,建议在样式中分别声明,并在 Canvas 绘制时明确指定粗细和字体族。这样可以避免浏览器自动合成字体,也可以让不同文本层级保持一致的视觉效果。下面整理了几类常见问题和处理建议。
| 现象 | 可能原因 | 处理建议 |
|---|---|---|
| 自定义字体不生效 | 字体族名称不一致,或字体文件无法访问 | 核对 @font-face 与 ctx.font 中的名称,检查资源地址 |
| 首次绘制出现默认字体 | 字体尚未加载完成就开始绘制 | 在 document.fonts.ready 完成后绘制,或提前加载字体 |
| 跨域字体加载失败 | 字体服务器未允许当前来源访问 | 配置跨域响应头,确保字体资源可被当前页面读取 |
| 字重或斜体表现不正确 | 未声明对应字重,或 Canvas 中未写明样式 | 分别声明不同字重,并在 ctx.font 中准确描述 |
从工程化角度看,字体资源应当纳入页面加载预算。较大的字体文件会影响首次绘制时间,因此可以选择子集化字体、按需加载字重、优先使用体积更小的字体格式。对于 Canvas 场景,还可以把字体加载状态纳入整体资源加载流程,与图片、配置文件等资源一起统一管理。
完整示例的组织方式
下面将示例拆分为页面结构、字体样式和绘制逻辑三个部分。页面结构提供画布节点,字体样式负责声明自定义字体,绘制逻辑负责等待字体就绪后再执行绘制。这种组织方式便于维护,也更接近真实项目中的模块划分。
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Canvas 自定义字体结构示例</title>
</head>
<body>
<canvas id="myCanvas" width="600" height="400"></canvas>
</body>
</html>
/* 字体样式:声明 Canvas 需要使用的自定义字体 */
@font-face {
font-family: 'MyCustomFont';
src: url('https://ipipp.com/fonts/my-custom-font.woff2') format('woff2'),
url('https://ipipp.com/fonts/my-custom-font.woff') format('woff');
font-weight: normal;
font-style: normal;
}
#myCanvas {
border: 1px solid #ccc;
}
// 获取画布和上下文
const canvas = document.getElementById('myCanvas');
const ctx = canvas.getContext('2d');
// 封装绘制逻辑,便于成功和失败时复用
function drawText(useCustomFont) {
// 清理画布,避免重复绘制残留
ctx.clearRect(0, 0, canvas.width, canvas.height);
// 根据字体加载结果选择字体
if (useCustomFont) {
ctx.font = '24px MyCustomFont, sans-serif';
} else {
ctx.font = '24px sans-serif';
}
// 绘制填充文本
ctx.fillStyle = '#333';
ctx.fillText('自定义字体异步加载示例', 50, 100);
// 绘制描边文本
ctx.strokeStyle = '#009900';
ctx.lineWidth = 1;
ctx.strokeText('自定义描边字体', 50, 150);
}
// 等待文档字体就绪后绘制
document.fonts.ready.then(() => {
drawText(true);
}).catch(() => {
drawText(false);
});
这种拆分方式的好处是职责清晰。页面结构只描述画布容器,样式负责字体资源,脚本负责加载状态和绘制结果。如果后续需要增加字重、斜体或更多字体族,只需要在样式中扩展声明,并在绘制逻辑中保持名称一致即可。
总体来看,要在 Canvas 中稳定使用自定义字体,需要把握三个要点:第一,通过 @font-face 将字体注册到文档;第二,通过 ctx.font 使用完全一致的字体族名称;第三,在字体加载完成后执行绘制,并为失败情况准备回退方案。围绕这三点建立检查流程,可以有效避免默认字体替代、文字闪烁和导出图片不一致等问题,使 Canvas 文本呈现更接近预期设计。
HTML_Canvas自定义字体异步加载font_facedrawText修改时间:2026-07-11 09:27:31