导读:本期聚焦于天马创作的《如何在HTML Canvas中应用自定义字体并实现异步加载》,敬请观看详情。在HTML Canvas开发中,默认字体往往无法满足个性化设计需求,很多开发者想知道如何给Canvas文本应用自定义字体,同时解决字体加载慢导致的渲染异常问题。本文会先介绍Canvas文本绘制的基础语法,再讲解自定义字体的引入方式,重点说明异步加载字体的实现逻辑,同时提供完整的代码示例和常见问题解决方案,帮助开发者快速掌握相关技巧,避免字体未加载完成就渲染文本导致的显示错误,提升Canvas绘图效果的稳定性。

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

Canvas 文本绘制的基础与 font 属性

Canvas 的二维上下文主要提供两个文本绘制方法,分别是 fillTextstrokeText。前者用于绘制填充颜色的文字,后者用于绘制描边文字。在调用这两个方法之前,通常需要先设置上下文的字体属性。Canvas 中的 ctx.font 属性接受类似 CSS font 简写规则的字符串,可以包含字体大小、字体族,也可以进一步描述粗细、斜体等样式。

在实际书写时,字体族名称是 Canvas 能否找到目标字体的关键。如果只写一个自定义字体名称,建议在其后追加通用字体族作为回退,例如 sans-serif。这样即使自定义字体暂时没有加载成功,或者名称书写存在偏差,文本仍然可以正常显示,不会出现完全无法阅读的情况。绘制文本时还需要配合 fillStylestrokeStylelineWidth 等属性,控制文字的颜色和描边效果。

下面是一个基础文本绘制示例,展示如何先设置字体,再分别绘制填充文本和描边文本。

// 获取画布元素和二维绘图上下文
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 用于指定字体文件来源。为了更好的浏览器兼容性,可以同时提供多个字体格式,例如 woff2woff。此外,font-weightfont-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 时才开始请求字体。例如,可以在页面初始化阶段使用 FontFaceload 方法主动加载字体,也可以让页面中某个不可见区域提前应用该字体。对于需要导出图片的 Canvas 应用,尤其要避免在导出前一刻才加载字体。

如果项目中使用多个字重或多个字体族,建议在样式中分别声明,并在 Canvas 绘制时明确指定粗细和字体族。这样可以避免浏览器自动合成字体,也可以让不同文本层级保持一致的视觉效果。下面整理了几类常见问题和处理建议。

现象可能原因处理建议
自定义字体不生效字体族名称不一致,或字体文件无法访问核对 @font-facectx.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

免责声明:​ 已尽一切努力确保本网站所含信息的准确性。网站内容多为原创整理与精心编撰,观点力求客观中立。本站旨在免费分享,内容仅供个人学习、研究或参考使用。若引用了第三方作品,版权归原作者所有。如内容涉及您的权益,请联系我们处理。
内容垂直聚焦
专注技术核心技术栏目,确保每篇文章深度聚焦于实用技能。从代码技巧到架构设计,为用户提供无干扰的纯技术知识沉淀,精准满足专业提升需求。
知识结构清晰
覆盖从开发到部署的全链路。AI、前端、编程、数据库、服务器、建站、系统层层递进,构建清晰学习路径,帮助用户系统化掌握开发与运维所需的核心技术。
深度技术解析
拒绝泛泛而谈,深入技术细节与实践难点。无论是数据库优化还是服务器配置,均结合真实场景与代码示例进行剖析,致力于提供可直接应用于工作的解决方案。
专业领域覆盖
精准对应开发生命周期。从前端界面到后端编程,从数据库操作到服务器运维,形成完整闭环,一站式满足全栈工程师和运维人员的技术需求。
即学即用高效
内容强调实操性,步骤清晰、代码完整。用户可根据教程直接复现和应用于自身项目,显著缩短从学习到实践的距离,快速解决开发中的具体问题。
持续更新保障
专注既定技术方向进行长期、稳定的内容输出。确保各栏目技术文章持续更新迭代,紧跟主流技术发展趋势,为用户提供经久不衰的学习价值。