作为一名资深前端架构师和图形渲染专家,我将带你深入探索 HTML5 的 <canvas> 元素。Canvas 提供了一个通过 JavaScript 绘制图形的 API,它允许你在网页上进行像素级别的渲染,包括绘制图形、处理图像、创建动画,甚至是构建复杂的游戏和数据可视化。
<canvas> 元素本身只是一个空白的、可供绘制的矩形区域,它没有绘图能力。所有的绘图操作都需要通过 JavaScript 来完成,通过获取其“渲染上下文”来操作像素。
你可以在 HTML 中创建 <canvas> 元素,并为其指定 id、width 和 height 属性。不设置 width 和 height 属性时,默认宽度为 300px,高度为 150px。
<canvas id="myCanvas" width="500" height="300">
您的浏览器不支持 Canvas。
</canvas>
<canvas> 标签之间的内容会在浏览器不支持 Canvas 时显示,提供良好的兼容性。
Canvas 提供了多种绘图上下文:
'2d': 用于绘制二维图形,这是最常用和本教程的重点。'webgl' / 'webgl2': 用于绘制三维图形,基于 OpenGL ES。'bitmaprenderer': 用于将 ImageBitmap 渲染到 canvas。const canvas = document.getElementById('myCanvas');
// 检查浏览器是否支持 Canvas
if (canvas.getContext) {
const ctx = canvas.getContext('2d'); // 获取 2D 绘图上下文
console.log('Canvas 2D 上下文已获取:', ctx);
// 第一次绘制:一个简单的矩形
ctx.fillStyle = 'blue'; // 设置填充颜色
ctx.fillRect(50, 50, 100, 75); // 绘制一个填充矩形 (x, y, width, height)
} else {
console.warn('您的浏览器不支持 Canvas。');
}
2D 绘图上下文 (CanvasRenderingContext2D) 提供了丰富的 API 来绘制路径、矩形、文本、图像等。
Canvas 的坐标系原点 (0,0) 位于左上角。X 轴正方向向右,Y 轴正方向向下。所有绘图操作都基于这个坐标系。
fillRect(x, y, width, height): 绘制一个填充矩形。strokeRect(x, y, width, height): 绘制一个矩形边框。clearRect(x, y, width, height): 清除指定矩形区域内的像素,使其完全透明。const canvas = document.getElementById('canvasRects');
const ctx = canvas.getContext('2d');
ctx.fillStyle = 'green';
ctx.fillRect(10, 10, 80, 80); // 填充矩形
ctx.strokeStyle = 'red';
ctx.lineWidth = 5;
ctx.strokeRect(100, 10, 80, 80); // 边框矩形
ctx.clearRect(40, 40, 20, 20); // 清除中心区域
路径是 Canvas 绘图的基石。它们由一系列点和连接这些点的线段或曲线组成。
const canvas = document.getElementById('canvasPaths');
const ctx = canvas.getContext('2d');
// 绘制直线
ctx.beginPath(); // 开始一条新路径
ctx.moveTo(10, 10); // 移动到起点
ctx.lineTo(100, 10); // 绘制一条线到 (100, 10)
ctx.lineTo(100, 100);
ctx.lineTo(10, 100);
ctx.closePath(); // 闭合路径,将当前点与起始点连接
ctx.strokeStyle = 'purple';
ctx.lineWidth = 2;
ctx.stroke(); // 绘制路径边框
// 绘制圆
ctx.beginPath();
ctx.arc(200, 60, 50, 0, Math.PI * 2, true); // (x, y, radius, startAngle, endAngle, anticlockwise)
ctx.fillStyle = 'orange';
ctx.fill(); // 填充路径
// 绘制二次贝塞尔曲线
ctx.beginPath();
ctx.moveTo(300, 100); // 起始点
ctx.quadraticCurveTo(350, 10, 400, 100); // 控制点 (350, 10), 终点 (400, 100)
ctx.strokeStyle = 'teal';
ctx.lineWidth = 3;
ctx.stroke();
// 绘制三次贝塞尔曲线
ctx.beginPath();
ctx.moveTo(420, 100); // 起始点
ctx.bezierCurveTo(450, 20, 480, 180, 510, 100); // 控制点1, 控制点2, 终点
ctx.stroke();
Canvas 允许你绘制文本,并控制字体、大小、颜色、对齐方式等。
font: 设置字体样式 (例如 '24px Arial')。fillStyle / strokeStyle: 文本颜色。fillText(text, x, y, maxWidth): 填充文本。strokeText(text, x, y, maxWidth): 绘制文本边框。textAlign: 文本水平对齐 ('start', 'end', 'left', 'right', 'center')。textBaseline: 文本垂直对齐 ('top', 'hanging', 'middle', 'alphabetic', 'ideographic', 'bottom')。const canvas = document.getElementById('canvasText');
const ctx = canvas.getContext('2d');
ctx.font = '30px Comic Sans MS';
ctx.fillStyle = 'red';
ctx.textAlign = 'center';
ctx.fillText('Hello Canvas!', canvas.width / 2, 50);
ctx.font = '40px Arial';
ctx.strokeStyle = 'blue';
ctx.lineWidth = 1;
ctx.textAlign = 'left';
ctx.strokeText('Web Graphics', 10, 120);
你可以将图片、视频帧甚至另一个 Canvas 元素绘制到当前 Canvas 上。
drawImage(image, dx, dy): 在指定位置绘制图像。drawImage(image, dx, dy, dWidth, dHeight): 在指定位置和尺寸绘制图像。drawImage(image, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight): 从源图像的指定区域剪裁,然后绘制到目标位置和尺寸。const canvas = document.getElementById('canvasImage');
const ctx = canvas.getContext('2d');
const img = new Image();
img.src = 'https://via.placeholder.com/150'; // 替换为你的图片路径
img.onload = () => {
// 完整绘制图像
ctx.drawImage(img, 10, 10);
// 缩放绘制图像
ctx.drawImage(img, 170, 10, 80, 80);
// 裁剪并绘制图像
// 从原图 (50,50) 处裁剪 50x50 像素,绘制到 Canvas 的 (260,10) 处,尺寸为 100x100
ctx.drawImage(img, 50, 50, 50, 50, 260, 10, 100, 100);
};
img.onerror = () => {
console.error('图片加载失败,请检查图片路径。');
};
请确保网络连接,以便加载占位符图片。
Canvas 绘图上下文维护着一个状态堆栈,允许你保存和恢复当前的绘图状态(如填充/描边样式、变换矩阵、裁剪路径等)。
save() 和 restore()当你想应用临时的样式或变换,而不影响后续绘图时,这是非常有用的。
const canvas = document.getElementById('canvasState');
const ctx = canvas.getContext('2d');
// 默认状态
ctx.fillStyle = 'red';
ctx.fillRect(10, 10, 50, 50);
ctx.save(); // 保存当前状态 (fillStyle: red)
// 更改状态,只对当前和后续绘制有效,直到 restore
ctx.fillStyle = 'blue';
ctx.fillRect(80, 10, 50, 50);
ctx.restore(); // 恢复到之前保存的状态 (fillStyle: red)
ctx.fillRect(150, 10, 50, 50); // 再次绘制,会是红色
Canvas 提供了平移、旋转和缩放功能,它们会改变 Canvas 的坐标系。
translate(x, y): 平移坐标系原点。rotate(angle): 旋转坐标系(角度为弧度)。scale(x, y): 缩放坐标系。transform(a, b, c, d, e, f): 直接应用变换矩阵。setTransform(a, b, c, d, e, f): 重置并应用变换矩阵。resetTransform(): 重置为默认变换(单位矩阵)。const canvas = document.getElementById('canvasTransform');
const ctx = canvas.getContext('2d');
ctx.fillStyle = 'blue';
ctx.fillRect(10, 10, 50, 50); // 原始矩形
// 平移
ctx.save();
ctx.translate(100, 0); // X轴平移 100px
ctx.fillStyle = 'green';
ctx.fillRect(10, 10, 50, 50);
ctx.restore();
// 旋转
ctx.save();
ctx.translate(250, 50); // 将旋转中心移到矩形中心
ctx.rotate(Math.PI / 4); // 旋转 45 度 (π/4 弧度)
ctx.fillStyle = 'orange';
ctx.fillRect(-25, -25, 50, 50); // 围绕新原点绘制
ctx.restore();
// 缩放
ctx.save();
ctx.translate(350, 10);
ctx.scale(1.5, 0.8); // X轴放大1.5倍,Y轴缩小0.8倍
ctx.fillStyle = 'purple';
ctx.fillRect(10, 10, 50, 50);
ctx.restore();
Canvas 支持线性渐变和径向渐变。
createLinearGradient(x0, y0, x1, y1): 创建线性渐变。createRadialGradient(x0, y0, r0, x1, y1, r1): 创建径向渐变。addColorStop(offset, color): 为渐变添加颜色停止点。const canvas = document.getElementById('canvasGradients');
const ctx = canvas.getContext('2d');
// 线性渐变
const linearGrad = ctx.createLinearGradient(0, 0, 200, 0); // 从左到右
linearGrad.addColorStop(0, 'red');
linearGrad.addColorStop(0.5, 'yellow');
linearGrad.addColorStop(1, 'green');
ctx.fillStyle = linearGrad;
ctx.fillRect(10, 10, 200, 80);
// 径向渐变
const radialGrad = ctx.createRadialGradient(300, 50, 10, 300, 50, 70); // 从中心向外
radialGrad.addColorStop(0, 'white');
radialGrad.addColorStop(0.5, 'cyan');
radialGrad.addColorStop(1, 'blue');
ctx.fillStyle = radialGrad;
ctx.beginPath();
ctx.arc(300, 50, 70, 0, Math.PI * 2);
ctx.fill();
shadowOffsetX / shadowOffsetY: 阴影的水平/垂直偏移。shadowBlur: 阴影模糊度。shadowColor: 阴影颜色。const canvas = document.getElementById('canvasShadows');
const ctx = canvas.getContext('2d');
ctx.shadowOffsetX = 5;
ctx.shadowOffsetY = 5;
ctx.shadowBlur = 10;
ctx.shadowColor = 'rgba(0, 0, 0, 0.5)'; // 半透明黑色阴影
ctx.fillStyle = 'lightblue';
ctx.fillRect(20, 20, 100, 60);
ctx.font = '40px Arial';
ctx.fillStyle = 'darkblue';
ctx.fillText('Shadow Text', 150, 70);
裁剪路径可以将绘制限制在某个形状内部。
const canvas = document.getElementById('canvasClip');
const ctx = canvas.getContext('2d');
// 绘制一个圆形作为裁剪路径
ctx.beginPath();
ctx.arc(100, 75, 60, 0, Math.PI * 2);
ctx.clip(); // 应用裁剪路径
// 在裁剪路径内部绘制一个大矩形,只有圆形部分会显示
ctx.fillStyle = 'pink';
ctx.fillRect(0, 0, 200, 150);
// 绘制一个图像,也只会在圆形内部显示
const img = new Image();
img.src = 'https://via.placeholder.com/150/ff69b4/ffffff?text=Clipped';
img.onload = () => {
ctx.drawImage(img, 50, 25, 100, 100);
};
Canvas 动画通过不断地擦除、重绘和更新来实现。requestAnimationFrame 是实现流畅动画的最佳方式。
const canvas = document.getElementById('canvasAnimation');
const ctx = canvas.getContext('2d');
let x = 0;
let dx = 2; // 移动速度
function animate() {
ctx.clearRect(0, 0, canvas.width, canvas.height); // 清除整个画布
ctx.fillStyle = 'red';
ctx.beginPath();
ctx.arc(x, 50, 20, 0, Math.PI * 2);
ctx.fill();
x += dx; // 更新位置
// 边界检测
if (x + 20 > canvas.width || x - 20 < 0) {
dx = -dx; // 反向
}
requestAnimationFrame(animate); // 请求下一帧动画
}
animate(); // 启动动画
可以通过监听 Canvas 元素的鼠标或触摸事件来实现交互。
const canvas = document.getElementById('canvasInteraction');
const ctx = canvas.getContext('2d');
let isDrawing = false;
let lastX = 0;
let lastY = 0;
canvas.addEventListener('mousedown', (e) => {
isDrawing = true;
[lastX, lastY] = [e.offsetX, e.offsetY]; // 获取鼠标相对于 Canvas 左上角的坐标
});
canvas.addEventListener('mousemove', (e) => {
if (!isDrawing) return;
ctx.beginPath();
ctx.moveTo(lastX, lastY);
ctx.lineTo(e.offsetX, e.offsetY);
ctx.strokeStyle = 'black';
ctx.lineWidth = 3;
ctx.lineCap = 'round'; // 设置线条端点样式
ctx.stroke();
[lastX, lastY] = [e.offsetX, e.offsetY];
});
canvas.addEventListener('mouseup', () => {
isDrawing = false;
});
canvas.addEventListener('mouseout', () => { // 鼠标移出画布时也停止绘制
isDrawing = false;
});
在画布上按住鼠标左键拖动以自由绘制。
toDataURL(type, encoderOptions): 将 Canvas 内容导出为 Data URL 格式的图片。toBlob(callback, type, encoderOptions): 将 Canvas 内容导出为 Blob 对象(通常用于上传)。const canvas = document.getElementById('myCanvas');
// ... 在 Canvas 上绘制一些东西 ...
// 导出为 PNG
const pngDataUrl = canvas.toDataURL('image/png');
console.log('PNG Data URL:', pngDataUrl.substring(0, 50) + '...'); // 太长,只显示一部分
// 导出为 JPEG (质量 0.8)
const jpegDataUrl = canvas.toDataURL('image/jpeg', 0.8);
// 创建下载链接
const downloadLink = document.createElement('a');
downloadLink.href = pngDataUrl;
downloadLink.download = 'my_drawing.png';
downloadLink.textContent = '下载我的画作';
document.body.appendChild(downloadLink);
// 导出为 Blob (异步)
canvas.toBlob((blob) => {
const file = new File([blob], 'my_drawing.jpeg', { type: 'image/jpeg' });
console.log('Blob 文件:', file);
// 可以将 Blob 发送到服务器
}, 'image/jpeg', 0.9);
可以直接访问和修改 Canvas 上的像素数据,实现图像处理效果。
getImageData(sx, sy, sWidth, sHeight): 获取指定区域的 ImageData 对象。putImageData(imageData, dx, dy): 将 ImageData 对象绘制到 Canvas。createImageData(width, height): 创建空的 ImageData 对象。ImageData 对象包含 width、height 属性和一个 data 属性。data 是一个 Uint8ClampedArray,存储了每个像素的 RGBA 值(每四个元素代表一个像素:红、绿、蓝、透明度,值范围 0-255)。
const canvas = document.getElementById('canvasPixels');
const ctx = canvas.getContext('2d');
// 绘制一个矩形
ctx.fillStyle = 'red';
ctx.fillRect(0, 0, 50, 50);
// 获取像素数据
const imageData = ctx.getImageData(0, 0, 50, 50);
const pixels = imageData.data; // Uint8ClampedArray
// 遍历像素数据,将所有像素的 R 值设为 0 (去掉红色通道)
for (let i = 0; i < pixels.length; i += 4) {
pixels[i] = 0; // Red
// pixels[i + 1] = 0; // Green
// pixels[i + 2] = 0; // Blue
// pixels[i + 3] = 255; // Alpha
}
// 将修改后的像素数据放回 Canvas
ctx.putImageData(imageData, 60, 0); // 绘制到不同位置
requestAnimationFrame 替代 setInterval/setTimeout: 动画更流畅,并与浏览器刷新率同步。clearRect() 清除局部区域而不是整个画布,或者在每次绘制前用 fillRect() 覆盖背景。beginPath() 和 closePath() 的调用次数,一次性绘制多条线段。canvas.getContext('2d', { alpha: false }) 可以略微提升性能。HTML Canvas 是一个极其强大的工具,它赋予了 Web 浏览器在像素层面上进行自由创作的能力。从简单的图形绘制、文本渲染到复杂的动画、游戏开发、数据可视化,甚至是图像处理工具,Canvas 的应用场景无处不在。
随着 WebAssembly 和 WebGL/WebGPU 的发展,Canvas 在高性能图形渲染领域的潜力将进一步释放。掌握 Canvas,将为你在前端图形开发领域打开新的大门。
感谢您的阅读,希望本教程能帮助您在 Canvas 绘图领域更上一层楼!