pin_drop当前位置:知识文库 ❯ 图文
多媒体:绘制文本 - 完整教程与代码示例
一、教程简介
Canvas 不仅可以绘制图形,还提供了强大的文本渲染能力。通过 fillText 和 strokeText 可以在画布上绘制填充文本和描边文本,配合 font、textAlign、textBaseline 等属性可以精确控制文本的字体、大小、对齐方式和基线位置。本教程将全面讲解 Canvas 文本绘制的各个方面。
二、核心概念
文本绘制方法
文本样式属性
文本测量
三、语法与用法
fillText - 填充文本
代码示例
ctx.fillText(text, x, y);
ctx.fillText(text, x, y, maxWidth);strokeText - 描边文本
代码示例
ctx.strokeText(text, x, y);
ctx.strokeText(text, x, y, maxWidth);
参数与 fillText 相同。
font - 字体设置
代码示例
ctx.font = 'bold 24px "Microsoft YaHei", sans-serif';
遵循 CSS font 简写语法:style variant weight size/line-height family
代码示例
// 完整语法
ctx.font = 'italic small-caps bold 24px/1.5 "Microsoft YaHei", sans-serif';
// 常用写法
ctx.font = '24px sans-serif'; // 仅大小和字体
ctx.font = 'bold 24px serif'; // 加粗
ctx.font = 'italic 18px monospace'; // 斜体textAlign - 水平对齐
代码示例
ctx.textAlign = 'left'; // 左对齐
ctx.textAlign = 'right'; // 右对齐
ctx.textAlign = 'center'; // 居中
ctx.textAlign = 'start'; // 默认,与文本方向一致(通常等同 left)
ctx.textAlign = 'end'; // 与文本方向相反(通常等同 right)
对齐基于 fillText/strokeText 中指定的 x 坐标。
textBaseline - 基线对齐
代码示例
ctx.textBaseline = 'top'; // 文本顶部对齐
ctx.textBaseline = 'hanging'; // 悬挂基线
ctx.textBaseline = 'middle'; // 文本垂直居中
ctx.textBaseline = 'alphabetic'; // 默认,字母基线
ctx.textBaseline = 'ideographic'; // 表意文字基线
ctx.textBaseline = 'bottom'; // 文本底部对齐
基线基于 fillText/strokeText 中指定的 y 坐标。
measureText - 文本测量
代码示例
const metrics = ctx.measureText('Hello World');
console.log(metrics.width); // 文本宽度
console.log(metrics.actualBoundingBoxAscent); // 实际上边界
console.log(metrics.actualBoundingBoxDescent); // 实际下边界四、代码示例
示例1:文本样式与对齐全面演示
代码示例
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>文本样式与对齐全面演示</title>
<style>
body {
margin: 0;
padding: 20px;
background: #0d1117;
color: #c9d1d9;
font-family: 'Segoe UI', sans-serif;
display: flex;
flex-direction: column;
align-items: center;
}
canvas {
border: 1px solid #30363d;
border-radius: 8px;
background: #161b22;
}
</style>
</head>
<body>
<h1>文本样式与对齐全面演示</h1>
<canvas id="myCanvas" width="750" height="600"></canvas>
<script>
const canvas = document.getElementById('myCanvas');
const ctx = canvas.getContext('2d');
// === 1. fillText vs strokeText ===
ctx.fillStyle = '#c9d1d9';
ctx.font = 'bold 14px sans-serif';
ctx.textAlign = 'center';
ctx.fillText('fillText vs strokeText', 375, 25);
// 填充文本
ctx.font = 'bold 36px sans-serif';
ctx.fillStyle = '#58a6ff';
ctx.fillText('Fill Text', 150, 75);
// 描边文本
ctx.strokeStyle = '#3fb950';
ctx.lineWidth = 2;
ctx.strokeText('Stroke Text', 450, 75);
// 填充+描边
ctx.fillStyle = '#0f3460';
ctx.fillText('Both', 375, 120);
ctx.strokeStyle = '#58a6ff';
ctx.lineWidth = 1;
ctx.strokeText('Both', 375, 120);
// === 2. textAlign 演示 ===
ctx.fillStyle = '#c9d1d9';
ctx.font = 'bold 14px sans-serif';
ctx.fillText('textAlign 水平对齐', 375, 165);
// 参考线
ctx.strokeStyle = '#e94560';
ctx.lineWidth = 1;
ctx.setLineDash([4, 4]);
ctx.beginPath();
ctx.moveTo(375, 180);
ctx.lineTo(375, 280);
ctx.stroke();
ctx.setLineDash([]);
const aligns = ['left', 'center', 'right', 'start', 'end'];
aligns.forEach((align, i) => {
const y = 200 + i * 20;
ctx.textAlign = align;
ctx.fillStyle = '#8b949e';
ctx.font = '14px sans-serif';
ctx.fillText('textAlign = ' + align, 375, y);
});
ctx.textAlign = 'center';
// === 3. textBaseline 演示 ===
ctx.fillStyle = '#c9d1d9';
ctx.font = 'bold 14px sans-serif';
ctx.fillText('textBaseline 基线对齐', 375, 310);
// 参考线
ctx.strokeStyle = '#e94560';
ctx.lineWidth = 1;
ctx.setLineDash([4, 4]);
ctx.beginPath();
ctx.moveTo(50, 370);
ctx.lineTo(700, 370);
ctx.stroke();
ctx.setLineDash([]);
const baselines = ['top', 'hanging', 'middle', 'alphabetic', 'ideographic', 'bottom'];
baselines.forEach((baseline, i) => {
const x = 70 + i * 110;
ctx.textBaseline = baseline;
ctx.textAlign = 'center';
ctx.fillStyle = '#58a6ff';
ctx.font = '16px sans-serif';
ctx.fillText('Agp', x, 370);
ctx.fillStyle = '#8b949e';
ctx.font = '10px sans-serif';
ctx.textBaseline = 'top';
ctx.fillText(baseline, x, 385);
});
ctx.textBaseline = 'alphabetic';
// === 4. font 样式演示 ===
ctx.fillStyle = '#c9d1d9';
ctx.font = 'bold 14px sans-serif';
ctx.textBaseline = 'alphabetic';
ctx.fillText('font 字体样式', 375, 430);
const fonts = [
{ font: '16px sans-serif', label: '16px sans-serif' },
{ font: 'bold 20px serif', label: 'bold 20px serif' },
{ font: 'italic 18px monospace', label: 'italic 18px monospace' },
{ font: '24px "Courier New"', label: '24px Courier New' },
{ font: 'bold italic 22px Georgia', label: 'bold italic 22px Georgia' }
];
fonts.forEach((item, i) => {
const y = 455 + i * 28;
ctx.font = item.font;
ctx.fillStyle = '#58a6ff';
ctx.textAlign = 'left';
ctx.fillText('Hello 你好', 50, y);
ctx.font = '11px sans-serif';
ctx.fillStyle = '#8b949e';
ctx.textAlign = 'right';
ctx.fillText(item.label, 700, y);
});
ctx.textAlign = 'center';
// === 5. maxWidth 缩放 ===
ctx.fillStyle = '#c9d1d9';
ctx.font = 'bold 14px sans-serif';
ctx.textAlign = 'left';
ctx.fillText('maxWidth 文本缩放', 50, 455);
ctx.font = '24px sans-serif';
ctx.fillStyle = '#f8b500';
const maxW = [500, 300, 200, 100];
maxW.forEach((w, i) => {
const y = 510 + i * 25;
ctx.fillText('Hello Canvas Text', 50, y, w);
ctx.font = '10px sans-serif';
ctx.fillStyle = '#8b949e';
ctx.fillText('maxWidth=' + w, 560, y);
ctx.font = '24px sans-serif';
ctx.fillStyle = '#f8b500';
});
</script>
</body>
</html>示例2:文本特效与实际应用
代码示例
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>文本特效与实际应用</title>
<style>
body {
margin: 0;
padding: 20px;
background: #1a1a2e;
color: #eee;
font-family: 'Segoe UI', sans-serif;
display: flex;
flex-direction: column;
align-items: center;
}
canvas {
border: 1px solid #333;
border-radius: 8px;
background: #16213e;
}
</style>
</head>
<body>
<h1>文本特效与实际应用</h1>
<canvas id="myCanvas" width="700" height="500"></canvas>
<script>
const canvas = document.getElementById('myCanvas');
const ctx = canvas.getContext('2d');
// === 1. 渐变文字 ===
const gradText = '渐变文字';
ctx.font = 'bold 48px sans-serif';
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
const gradient = ctx.createLinearGradient(100, 40, 300, 40);
gradient.addColorStop(0, '#e94560');
gradient.addColorStop(0.5, '#f8b500');
gradient.addColorStop(1, '#3fb950');
ctx.fillStyle = gradient;
ctx.fillText(gradText, 200, 50);
// === 2. 描边文字 ===
ctx.font = 'bold 48px sans-serif';
ctx.strokeStyle = '#58a6ff';
ctx.lineWidth = 2;
ctx.strokeText('描边文字', 500, 50);
// === 3. 阴影文字 ===
ctx.shadowColor = '#e94560';
ctx.shadowBlur = 15;
ctx.shadowOffsetX = 3;
ctx.shadowOffsetY = 3;
ctx.fillStyle = '#fff';
ctx.font = 'bold 40px sans-serif';
ctx.fillText('阴影文字', 350, 130);
ctx.shadowColor = 'transparent';
ctx.shadowBlur = 0;
ctx.shadowOffsetX = 0;
ctx.shadowOffsetY = 0;
// === 4. 立体文字 ===
ctx.font = 'bold 48px sans-serif';
ctx.textAlign = 'center';
const depth = 5;
for (let i = depth; i > 0; i--) {
ctx.fillStyle = `rgba(88, 166, 255, ${0.15 * i})`;
ctx.fillText('立体文字', 350 + i * 2, 220 + i * 2);
}
ctx.fillStyle = '#58a6ff';
ctx.fillText('立体文字', 350, 220);
// === 5. 文本换行 ===
ctx.fillStyle = '#c9d1d9';
ctx.font = 'bold 14px sans-serif';
ctx.textAlign = 'left';
ctx.fillText('文本自动换行:', 50, 280);
function wrapText(ctx, text, x, y, maxWidth, lineHeight) {
const lines = text.split('\n');
let currentY = y;
lines.forEach(line => {
let words = line.split('');
let currentLine = '';
for (let i = 0; i < words.length; i++) {
const testLine = currentLine + words[i];
const metrics = ctx.measureText(testLine);
if (metrics.width > maxWidth && i > 0) {
ctx.fillText(currentLine, x, currentY);
currentLine = words[i];
currentY += lineHeight;
} else {
currentLine = testLine;
}
}
ctx.fillText(currentLine, x, currentY);
currentY += lineHeight;
});
return currentY;
}
ctx.font = '14px sans-serif';
ctx.fillStyle = '#8b949e';
wrapText(ctx, 'Canvas 本身不支持文本自动换行,需要通过 measureText 方法手动实现。这段文字演示了如何将长文本按照指定宽度自动换行显示。', 50, 305, 280, 22);
// === 6. 文本居中绘制 ===
ctx.fillStyle = '#c9d1d9';
ctx.font = 'bold 14px sans-serif';
ctx.fillText('文本居中绘制:', 450, 280);
// 绘制按钮
function drawButton(ctx, x, y, w, h, text, bgColor, textColor) {
// 背景
ctx.beginPath();
const r = 6;
ctx.moveTo(x + r, y);
ctx.arcTo(x + w, y, x + w, y + h, r);
ctx.arcTo(x + w, y + h, x, y + h, r);
ctx.arcTo(x, y + h, x, y, r);
ctx.arcTo(x, y, x + w, y, r);
ctx.closePath();
ctx.fillStyle = bgColor;
ctx.fill();
// 文本居中
ctx.fillStyle = textColor;
ctx.font = 'bold 14px sans-serif';
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillText(text, x + w / 2, y + h / 2);
}
drawButton(ctx, 400, 300, 120, 40, 'Primary', '#58a6ff', '#fff');
drawButton(ctx, 540, 300, 120, 40, 'Success', '#3fb950', '#fff');
drawButton(ctx, 400, 360, 120, 40, 'Warning', '#f8b500', '#000');
drawButton(ctx, 540, 360, 120, 40, 'Danger', '#e94560', '#fff');
// === 7. 文本测量 ===
ctx.textAlign = 'left';
ctx.textBaseline = 'alphabetic';
ctx.fillStyle = '#c9d1d9';
ctx.font = 'bold 14px sans-serif';
ctx.fillText('measureText 文本测量:', 400, 420);
ctx.font = '24px sans-serif';
const testStr = 'Hello Canvas';
const metrics = ctx.measureText(testStr);
ctx.fillStyle = '#58a6ff';
ctx.fillText(testStr, 420, 455);
// 宽度标注
ctx.strokeStyle = '#e94560';
ctx.lineWidth = 1;
ctx.beginPath();
ctx.moveTo(420, 462);
ctx.lineTo(420 + metrics.width, 462);
ctx.stroke();
ctx.fillStyle = '#8b949e';
ctx.font = '11px sans-serif';
ctx.fillText('width: ' + metrics.width.toFixed(2) + 'px', 420, 480);
</script>
</body>
</html>五、浏览器兼容性
六、注意事项与最佳实践
1. 字体加载
Canvas 使用的字体必须已经加载完成,否则会使用降级字体:
代码示例
// 等待字体加载完成
document.fonts.ready.then(function() {
ctx.font = '24px "Custom Font"';
ctx.fillText('Hello', 100, 100);
});
// 或者使用 FontFace API
const font = new FontFace('CustomFont', 'url(font.woff2)');
font.load().then(function(loadedFont) {
document.fonts.add(loadedFont);
ctx.font = '24px CustomFont';
ctx.fillText('Hello', 100, 100);
});2. 文本不支持换行
Canvas 的 fillText 不会自动换行,需要手动实现:
代码示例
function wrapText(ctx, text, x, y, maxWidth, lineHeight) {
let line = '';
for (let i = 0; i < text.length; i++) {
const testLine = line + text[i];
if (ctx.measureText(testLine).width > maxWidth) {
ctx.fillText(line, x, y);
line = text[i];
y += lineHeight;
} else {
line = testLine;
}
}
ctx.fillText(line, x, y);
}3. 文本居中的正确方式
代码示例
// 水平居中
ctx.textAlign = 'center';
ctx.fillText(text, centerX, y);
// 垂直居中
ctx.textBaseline = 'middle';
ctx.fillText(text, x, centerY);
// 完全居中
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillText(text, centerX, centerY);4. 文本与矩形对齐
代码示例
// 在矩形中居中文字
function drawTextInRect(ctx, text, rx, ry, rw, rh) {
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillText(text, rx + rw / 2, ry + rh / 2);
}5. 性能考虑
文本绘制相对较慢,对于大量文本或频繁更新,考虑:
-
离屏缓存:使用离屏 Canvas 缓存静态文本
-
减少切换:减少字体切换次数
-
避免频繁测量:避免在动画循环中频繁调用
measureText
七、代码规范示例
代码示例
// 推荐:封装文本绘制函数
function drawText(ctx, text, x, y, options) {
const {
font = '14px sans-serif',
fill = '#000000',
stroke = null,
lineWidth = 1,
align = 'left',
baseline = 'alphabetic',
maxWidth = undefined,
shadow = null
} = options || {};
ctx.font = font;
ctx.textAlign = align;
ctx.textBaseline = baseline;
if (shadow) {
ctx.shadowColor = shadow.color || 'rgba(0,0,0,0.5)';
ctx.shadowBlur = shadow.blur || 4;
ctx.shadowOffsetX = shadow.offsetX || 2;
ctx.shadowOffsetY = shadow.offsetY || 2;
}
if (fill) {
ctx.fillStyle = fill;
ctx.fillText(text, x, y, maxWidth);
}
if (stroke) {
ctx.strokeStyle = stroke;
ctx.lineWidth = lineWidth;
ctx.strokeText(text, x, y, maxWidth);
}
// 重置阴影
if (shadow) {
ctx.shadowColor = 'transparent';
ctx.shadowBlur = 0;
ctx.shadowOffsetX = 0;
ctx.shadowOffsetY = 0;
}
}
// 推荐:文本自动换行
function drawWrappedText(ctx, text, x, y, maxWidth, lineHeight, options) {
const { font = '14px sans-serif', fill = '#000000' } = options || {};
ctx.font = font;
ctx.fillStyle = fill;
ctx.textAlign = 'left';
ctx.textBaseline = 'top';
let currentY = y;
let line = '';
for (let i = 0; i < text.length; i++) {
const ch = text[i];
if (ch === '\n') {
ctx.fillText(line, x, currentY);
line = '';
currentY += lineHeight;
continue;
}
const testLine = line + ch;
if (ctx.measureText(testLine).width > maxWidth && line.length > 0) {
ctx.fillText(line, x, currentY);
line = ch;
currentY += lineHeight;
} else {
line = testLine;
}
}
if (line) ctx.fillText(line, x, currentY);
return currentY + lineHeight;
}
// 使用示例
drawText(ctx, 'Hello World', 100, 100, {
font: 'bold 24px sans-serif',
fill: '#58a6ff',
align: 'center',
baseline: 'middle',
shadow: { color: 'rgba(0,0,0,0.3)', blur: 8, offsetX: 2, offsetY: 2 }
});
drawWrappedText(ctx, '这是一段很长的文本,需要自动换行显示。Canvas 本身不支持自动换行。', 50, 200, 300, 22, {
font: '14px sans-serif',
fill: '#c9d1d9'
});八、常见问题与解决方案
常见问题
Canvas 上的中文显示异常怎么办?
原因是字体不支持中文或字体未加载完成。解决方案是使用支持中文的字体(如 "Microsoft YaHei"、"PingFang SC"),并通过 document.fonts.ready 等待字体加载完成后再绘制文本。
如何获取文本的精确高度?
使用 measureText 方法获取 actualBoundingBoxAscent 和 actualBoundingBoxDescent,两者之和即为文本精确高度。如果浏览器不支持这些属性,可降级使用 fontBoundingBoxAscent + fontBoundingBoxDescent。
文本模糊怎么办?
原因是高 DPI 屏幕未处理设备像素比。解决方案是通过 window.devicePixelRatio 获取设备像素比,将 Canvas 的宽高设置为显示尺寸乘以 DPR,再通过 ctx.scale(dpr, dpr) 缩放上下文。
如何实现文本垂直排列?
方案一:逐字绘制,通过循环遍历每个字符,依次在不同 y 坐标上绘制。方案二:使用 Canvas 变换,通过 ctx.rotate(-Math.PI / 2) 旋转画布后绘制文本。
maxWidth 参数导致文字变形怎么办?
maxWidth 会等比缩放文字导致变形。替代方案:方案一是截断文本并在末尾添加省略号;方案二是逐步缩小字体大小直到文本宽度满足要求。
九、总结
Canvas 文本绘制是界面开发的重要技能,关键要点:
-
fillText/strokeText:分别绘制填充文本和描边文本,可组合使用
-
font:使用 CSS font 语法设置字体,注意中文字体兼容性
-
textAlign:控制水平对齐,基于 x 坐标
-
textBaseline:控制基线对齐,基于 y 坐标
-
measureText:测量文本宽度,用于换行和布局计算
-
maxWidth:限制文本宽度,文本会等比缩放
-
文本换行:Canvas 不支持自动换行,需手动实现
-
字体加载:确保字体加载完成后再绘制文本
小贴士
下一教程将介绍 Canvas 渐变的使用。渐变可以为文本和图形添加丰富的色彩效果,包括线性渐变(createLinearGradient)和径向渐变(createRadialGradient),是提升 Canvas 视觉效果的重要手段。
本文涉及AI创作
内容由AI创作,请仔细甄别