别再死记硬背了!这份HTML5 Canvas入门教程让你秒懂绘图原理
本文详细讲解Canvas的绘图原理、基本操作、动画实现、性能优化以及WebGL、图像处理、物理引擎等进阶主题,包含丰富的代码示例和最佳实践,适合初学者和有一定经验的开发者阅读。

Canvas是HTML5最具代表性的特性之一,它为Web页面带来了动态图形和图像处理的能力。无论是数据可视化、游戏开发、图像编辑还是创意编程,Canvas都扮演着核心角色。本文将带领您系统学习Canvas的基础知识,并深入探讨其高级用法,帮助您全面掌握这一强大的绘图技术。
Canvas 是一个矩形区域的容器,通过 JavaScript 在区域内绘制图形。在 HTML 中定义 Canvas 非常简单:
<canvas id="myCanvas" width="800" height="600">您的浏览器不支持 Canvas,请升级或更换浏览器。</canvas>
width 和 height 属性设置画布的像素尺寸(注意:CSS 设置的宽高会影响显示比例,但绘图区域仍以属性值为准)。
标签内的文本是降级内容,仅在浏览器不支持 Canvas 时显示。
要真正开始绘图,必须获取 Canvas 的绘图上下文(context)。2D 上下文提供了所有绘制方法。
const canvas = document.getElementById('myCanvas');const ctx = canvas.getContext('2d');
Canvas 提供了三个绘制矩形的方法:fillRect、strokeRect 和 clearRect。
// 填充矩形ctx.fillStyle = 'red';ctx.fillRect(10, 10, 100, 50); // (x, y, width, height)// 描边矩形ctx.strokeStyle = 'blue';ctx.lineWidth = 2;ctx.strokeRect(120, 10, 100, 50);// 清除矩形区域ctx.clearRect(20, 20, 80, 30); // 擦除部分区域
路径是绘制复杂图形的基础。通过 beginPath() 开始新路径,然后使用 moveTo、lineTo 等方法定义路径,最后调用 stroke() 或 fill() 进行绘制。
ctx.beginPath();ctx.moveTo(50, 50); // 起点ctx.lineTo(150, 50); // 画到 (150,50)ctx.lineTo(100, 150); // 画到 (100,150)ctx.closePath(); // 闭合路径(回到起点)ctx.stroke(); // 描边// 也可以 ctx.fill() 填充
使用 arc() 方法绘制圆弧或圆。
ctx.beginPath();ctx.arc(200, 200, 50, 0, Math.PI * 2); // 圆心 (200,200),半径50,起始角0,结束角2πctx.fillStyle = 'green';ctx.fill();
颜色设置
fillStyle 和 strokeStyle 可以接受颜色字符串、渐变色或图案。
ctx.fillStyle = 'red'; // 颜色名称ctx.fillStyle = '#ff0000'; // 十六进制ctx.fillStyle = 'rgb(255,0,0)'; // RGBctx.fillStyle = 'rgba(255,0,0,0.5)'; // RGBA 带透明度
Canvas 支持线性渐变和径向渐变。
// 线性渐变const gradient = ctx.createLinearGradient(0, 0, 200, 0); // 从 (0,0) 到 (200,0)gradient.addColorStop(0, 'red');gradient.addColorStop(1, 'blue');ctx.fillStyle = gradient;ctx.fillRect(10, 10, 200, 100);
// 径向渐变const radialGradient = ctx.createRadialGradient(100, 100, 0, 100, 100, 50);radialGradient.addColorStop(0, 'white');radialGradient.addColorStop(1, 'black');ctx.fillStyle = radialGradient;ctx.fillRect(250, 10, 200, 100);
使用 fillText 和 strokeText 绘制文本,并可通过 font、textAlign 等属性设置样式。
ctx.font = '30px Arial';ctx.fillStyle = 'black';ctx.textAlign = 'center';ctx.textBaseline = 'middle';ctx.fillText('Hello Canvas', canvas.width / 2, canvas.height / 2);
通过 drawImage 方法可以将 Image 对象、Canvas 元素或视频帧绘制到画布上。
const img = new Image();img.onload = function() {// 在 (0,0) 绘制原图ctx.drawImage(img, 0, 0);// 缩放绘制:在 (0,0) 绘制宽100、高100的图像ctx.drawImage(img, 0, 0, 100, 100);};img.src = 'image.png';
Canvas 支持平移、旋转、缩放等变换,且可以通过 save 和 restore 管理状态栈。
ctx.save(); // 保存当前状态ctx.translate(100, 100); // 平移ctx.rotate(Math.PI / 4); // 旋转 45°ctx.scale(2, 0.5); // 缩放// 绘制变换后的图形ctx.fillRect(0, 0, 50, 50);ctx.restore(); // 恢复到保存的状态
动画的核心是反复清除画布并重新绘制。使用 requestAnimationFrame 。
let x = 0;const speed = 2;function animate() {// 1. 清除画布ctx.clearRect(0, 0, canvas.width, canvas.height);// 2. 更新位置x += speed;if (x > canvas.width) x = 0;// 3. 绘制ctx.fillRect(x, 50, 50, 50);// 4. 请求下一帧requestAnimationFrame(animate);}animate();
Canvas 本身不记录绘制的图形,但可以通过坐标计算实现交互。
canvas.addEventListener('click', (event) => {const rect = canvas.getBoundingClientRect();const x = event.clientX - rect.left;const y = event.clientY - rect.top;// 检查点是否在路径内(需要提前构建路径)if (ctx.isPointInPath(x, y)) {console.log('点击了图形!');}});
预先在内存中的 Canvas 绘制复杂图形,然后快速复制到主画布上,减少重复计算。
const offCanvas = document.createElement('canvas');const offCtx = offCanvas.getContext('2d');
// 在离屏画布上绘制复杂图形offCtx.fillStyle = 'red';offCtx.fillRect(0, 0, 200, 200);// 在主画布上直接绘制离屏画布ctx.drawImage(offCanvas, 0, 0);
减少不必要的状态改变,尽量将相同样式的绘制集中处理。
ctx.save();ctx.fillStyle = 'red';ctx.strokeStyle = 'blue';for (let i = 0; i < 100; i++) {ctx.fillRect(i * 10, 0, 8, 8);}ctx.restore();
实现一个基本的鼠标绘图功能。
let isDrawing = false;let lastX = 0, lastY = 0;
canvas.addEventListener('mousedown', (e) => {isDrawing = true;[lastX, lastY] = [e.offsetX, e.offsetY];});
canvas.addEventListener('mousemove', (e) => {if (!isDrawing) return;ctx.beginPath();ctx.moveTo(lastX, lastY);ctx.lineTo(e.offsetX, e.offsetY);ctx.stroke();[lastX, lastY] = [e.offsetX, e.offsetY];});
canvas.addEventListener('mouseup', () => isDrawing = false);canvas.addEventListener('mouseleave', () => isDrawing = false);
在 Retina 等高清屏幕上,Canvas 可能模糊。需要根据设备像素比调整画布尺寸。
const dpr = window.devicePixelRatio || 1;const displayWidth = canvas.clientWidth;const displayHeight = canvas.clientHeight;canvas.width = displayWidth * dpr;canvas.height = displayHeight * dpr;canvas.style.width = displayWidth + 'px';canvas.style.height = displayHeight + 'px';ctx.scale(dpr, dpr);
无论简单还是复杂的绘图,都遵循一个清晰的流程:
获取上下文(2D 或 WebGL)。
设置样式(颜色、线宽、渐变等)。
构建路径(或直接调用绘图方法)。
执行绘制(填充、描边等)。
动画循环(如需动效,重复以上步骤)。
这一流程体现了 Canvas 的“即时模式”绘图思想,每次绘制都是直接操作像素,不保留图形对象。
是的,Canvas 完全依赖 JavaScript 实现绘图。
<canvas> 标签本身只是一个空容器,不提供任何绘图能力。真正的绘制工作必须通过 JavaScript 调用绘图 API 完成。如果禁用 JavaScript,Canvas 区域将是一片空白(除非有降级内容)。这种设计使得 Canvas 极其灵活,可以与用户交互、动态生成内容,并与各种 JavaScript 库无缝集成。
通过 getContext('webgl') 可以获取 WebGL 上下文,利用 GPU 加速渲染 3D 场景。以下是一个极简的三角形绘制示例:
const gl = canvas.getContext('webgl');const vsSource = `attribute vec4 aPosition;void main() {gl_Position = aPosition;}`;const fsSource = `void main() {gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);}`;// 编译着色器、创建程序、绑定缓冲区等(详细代码略)// 最后绘制gl.drawArrays(gl.TRIANGLES, 0, 3);
WebGL 编程较为复杂,通常借助 Three.js 等库简化开发。
通过 getImageData 和 putImageData 可以逐像素操作图像,实现各种滤镜效果。
function applyGrayscale() {const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);const data = imageData.data;for (let i = 0; i < data.length; i += 4) {const gray = 0.299 * data[i] + 0.587 * data[i+1] + 0.114 * data[i+2];data[i] = data[i+1] = data[i+2] = gray;}ctx.putImageData(imageData, 0, 0);}
将 Canvas 与简单的物理模拟结合,可以创建逼真的运动效果。
class Ball {constructor(x, y, vx, vy) {this.x = x; this.y = y;this.vx = vx; this.vy = vy;this.radius = 10;}update() {this.vy += 0.5; // 重力this.x += this.vx;this.y += this.vy;// 边界反弹if (this.y + this.radius > canvas.height) {this.y = canvas.height - this.radius;this.vy *= -0.8;}}draw() {ctx.beginPath();ctx.arc(this.x, this.y, this.radius, 0, Math.PI*2);ctx.fillStyle = 'blue';ctx.fill();}}// 在动画循环中更新并绘制所有小球
Canvas 是绘制图表、地图等可视化内容的利器。可以封装一个简单的条形图组件:
class BarChart {constructor(canvas, data) {this.ctx = canvas.getContext('2d');this.data = data;this.width = canvas.width;this.height = canvas.height;}draw() {const max = Math.max(...this.data);const barWidth = this.width / this.data.length;this.data.forEach((value, i) => {const barHeight = (value / max) * this.height * 0.8;const x = i * barWidth;const y = this.height - barHeight;this.ctx.fillStyle = `hsl(${i * 30}, 70%, 50%)`;this.ctx.fillRect(x, y, barWidth - 2, barHeight);});}}
利用 Canvas 可以构建游戏循环和实体组件系统。以下是一个简单的游戏循环模板:
class Game {constructor(canvas) {this.ctx = canvas.getContext('2d');this.entities = [];this.lastTime = 0;}start() {this.gameLoop(performance.now());}gameLoop(now) {const delta = (now - this.lastTime) / 1000;this.lastTime = now;this.update(delta);this.render();requestAnimationFrame((t) => this.gameLoop(t));}update(delta) {this.entities.forEach(e => e.update(delta));}render() {this.ctx.clearRect(0, 0, canvas.width, canvas.height);this.entities.forEach(e => e.draw(this.ctx));}}
离屏渲染(OffscreenCanvas)可以在 Worker 线程中执行绘图,避免阻塞主线程。浏览器支持 OffscreenCanvas 接口。
// 主线程const offscreen = canvas.transferControlToOffscreen();const worker = new Worker('worker.js');worker.postMessage({ canvas: offscreen }, [offscreen]);
// worker.jsself.onmessage = (e) => {const canvas = e.data.canvas;const ctx = canvas.getContext('2d');// 在 Worker 中绘制ctx.fillStyle = 'red';ctx.fillRect(0, 0, 100, 100);};
Web Workers 处理像素数据:将耗时的图像处理任务交给 Worker,避免界面卡顿。
// 主线程const worker = new Worker('filter-worker.js');worker.postMessage(imageData);worker.onmessage = (e) => {ctx.putImageData(e.data, 0, 0);};
// filter-worker.jsself.onmessage = (e) => {const imageData = e.data;// 处理 imageData ...self.postMessage(imageData);};
Fabric.js:提供对象模型和交互能力,适合图形编辑器。
Konva.js:高性能 2D 绘图库,支持事件绑定和动画。
Paper.js:基于矢量图形的脚本框架,使用场景图。
PixiJS:2D WebGL 渲染引擎,性能卓越,适合游戏和交互应用。
Three.js:最流行的 3D 库,封装了 WebGL 细节。
这些库大大简化了复杂场景的开发,开发者可以根据需求选择合适的工具。
Canvas 是 Web 图形开发的基石,从简单的矩形绘制到复杂的 3D 渲染,它提供了丰富的 API 和无限的扩展可能。本文从基础概念出发,涵盖了元素创建、上下文获取、基本绘图、样式、变换、动画、交互和性能优化,随后深入探讨了 WebGL、图像处理、物理引擎、数据可视化、游戏框架以及现代库的应用。
掌握这些知识后,您将能够自信地使用 Canvas 构建各种创意项目,无论是数据可视化、小游戏还是图像处理工具。未来,随着 WebGPU 等新技术的出现,Canvas 的能力还将进一步扩展,值得持续关注和学习。
阅读原文:原文链接