Canvas文本自动换行需手动计算字符宽度并分段绘制,核心是逐字measureText、按像素宽切分、逐行渲染,须设好字体、处理中英文混排与边界优化。

Canvas 中文本自动换行不能靠 CSS,必须手动计算字符宽度并分段绘制。核心思路是:逐字测量、按最大宽度切分、逐行渲染。
基础原理:用 measureText 动态判断断点
Canvas 2D 上下文没有内置换行支持,需借助 ctx.measureText(text).width 获取字符串像素宽度,并与设定的可用宽度比较。关键不是“按字数切”,而是“按像素宽切”——尤其对中英文混排、全角/半角、不同字体影响显著。
- 先设置好字体(
ctx.font = '14px "Microsoft YaHei", sans-serif'),字体未生效时measureText结果不可靠 - 单个空格、标点、汉字、英文字母宽度都不同,不能假设等宽;建议以“字符”为单位递增拼接测试,而非“字数”或“字节”
- 避免每次循环都新建字符串,可维护一个临时字符串 + 当前宽度累加值,提升性能
实用换行算法(支持中英文混排)
以下是一个轻量、鲁棒的实现逻辑(不依赖外部库):
- 输入:待绘文本
str、画布上下文ctx、最大宽度maxWidth、行高lineHeight、起始坐标x, y - 将字符串转为字符数组(
Array.from(str)),确保正确处理 emoji 和代理对 - 初始化空行
line = '',当前行宽width = 0 - 遍历每个字符:
• 拼接line + char,测宽
• 若新宽度 ≤maxWidth,保留该字符,更新width
• 否则:绘制当前line,重置line = char(非空格优先断)、width = ctx.measureText(char).width
• 特殊处理:若单个字符本身超宽(如超长 URL 片段),强制按字符切,避免死循环 - 循环结束后绘制最后一行
增强体验的细节优化
纯算法只是起点,真实场景还需考虑可读性与容错:
立即学习“前端免费学习笔记(深入)”;
-
单词边界优先:英文单词内不拆(如
"JavaScript"不拆成"JavaScrip"+"t"),可在空格或标点后尝试回退断行 -
省略号截断:末行放不下时,用
line.slice(0, -1) + '…'并重测宽度,确保 “…” 在框内 -
忽略首尾空白:绘制前对每行
.trim(),避免因缩进或换行符导致空白行错位 -
缓存字体度量:若字体固定且文本量大,可预建常见字符宽度查表(如 ASCII + 常用汉字),减少重复调用
measureText
简单封装示例(可直接复用)
下面是一个精简版函数,支持基本自动换行和垂直布局:
function wrapText(ctx, text, x, y, maxWidth, lineHeight = 20) {
const chars = Array.from(text);
let line = '';
let width = 0;
let lines = [];
<p>for (let i = 0; i < chars.length; i++) {
const char = chars[i];
const testLine = line + char;
const testWidth = ctx.measureText(testLine).width;</p><pre class='brush:php;toolbar:false;'>if (testWidth <= maxWidth) {
line = testLine;
width = testWidth;
} else {
if (line) lines.push(line);
line = char;
width = ctx.measureText(char).width;
// 防止单字超宽卡死
if (width > maxWidth) {
line = char;
lines.push(line);
line = '';
width = 0;
}
}} if (line) lines.push(line);
lines.forEach((line, i) => { ctx.fillText(line.trim(), x, y + i * lineHeight); }); }
调用:wrapText(ctx, "欢迎使用Canvas文本渲染功能", 20, 50, 200, 24);



















