Canvas是HTML5最具代表性的特性之一,它为Web页面带来了动态图形和图像处理的能力。无论是数据可视化、游戏开发、图像编辑还是创意编程,Canvas都扮演着核心角色。本文将带领您系统学习Canvas的基础知识,并深入探讨其高级用法,帮助您全面掌握这一强大的绘图技术。
一、Canvas 基础
1.1 创建 Canvas 元素
Canvas 是一个矩形区域的容器,通过 JavaScript 在区域内绘制图形。在 HTML 中定义 Canvas 非常简单:
<canvas id="myCanvas" width="800" height="600">
您的浏览器不支持 Canvas,请升级或更换浏览器。
</canvas>width 和 height 属性设置画布的像素尺寸(注意:CSS 设置的宽高会影响显示比例,但绘图区域仍以属性值为准)。
标签内的文本是降级内容,仅在浏览器不支持 Canvas 时显示。
1.2 获取 2D 绘图上下文
要真正开始绘图,必须获取 Canvas 的绘图上下文(context)。2D 上下文提供了所有绘制方法。
const canvas = document.getElementById('myCanvas');
const ctx = canvas.getContext('2d');1.3 基本绘图操作
绘制矩形
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();1.4 样式和颜色
颜色设置
fillStyle 和 strokeStyle 可以接受颜色字符串、渐变色或图案。
ctx.fillStyle = 'red'; // 颜色名称
ctx.fillStyle = '#ff0000'; // 十六进制
ctx.fillStyle = 'rgb(255,0,0)'; // RGB
ctx.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);1.5 文本绘制
使用 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);1.6 图像操作
绘制图像
通过 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';1.7 变换操作
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(); // 恢复到保存的状态1.8 动画实现
动画的核心是反复清除画布并重新绘制。使用 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();1.9 事件处理
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('点击了图形!');
}
});1.10 性能优化技巧
离屏 Canvas
预先在内存中的 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();1.11 实际应用示例:简单绘图板
实现一个基本的鼠标绘图功能。
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);1.12 常见问题解决:高清屏适配
在 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);二、Canvas 进阶
2.1 Canvas 绘图的通用流程
无论简单还是复杂的绘图,都遵循一个清晰的流程:
获取上下文(2D 或 WebGL)。
设置样式(颜色、线宽、渐变等)。
构建路径(或直接调用绘图方法)。
执行绘制(填充、描边等)。
动画循环(如需动效,重复以上步骤)。
这一流程体现了 Canvas 的“即时模式”绘图思想,每次绘制都是直接操作像素,不保留图形对象。
2.2 Canvas 必须依赖 JavaScript 吗?
是的,Canvas 完全依赖 JavaScript 实现绘图。
<canvas> 标签本身只是一个空容器,不提供任何绘图能力。真正的绘制工作必须通过 JavaScript 调用绘图 API 完成。如果禁用 JavaScript,Canvas 区域将是一片空白(除非有降级内容)。这种设计使得 Canvas 极其灵活,可以与用户交互、动态生成内容,并与各种 JavaScript 库无缝集成。
3. 高阶用法
3.1 WebGL – 3D 图形渲染
通过 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 等库简化开发。
3.2 图像处理与滤镜
通过 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);
}3.3 物理引擎集成
将 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();
}
}
// 在动画循环中更新并绘制所有小球3.4 数据可视化
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);
});
}
}3.5 游戏开发框架
利用 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));
}
}3.6 高级性能优化
离屏渲染(OffscreenCanvas)可以在 Worker 线程中执行绘图,避免阻塞主线程。浏览器支持 OffscreenCanvas 接口。
// 主线程
const offscreen = canvas.transferControlToOffscreen();
const worker = new Worker('worker.js');
worker.postMessage({ canvas: offscreen }, [offscreen]);
// worker.js
self.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.js
self.onmessage = (e) => {
const imageData = e.data;
// 处理 imageData ...
self.postMessage(imageData);
};3.7 现代 Canvas 框架和库
Fabric.js:提供对象模型和交互能力,适合图形编辑器。
Konva.js:高性能 2D 绘图库,支持事件绑定和动画。
Paper.js:基于矢量图形的脚本框架,使用场景图。
PixiJS:2D WebGL 渲染引擎,性能卓越,适合游戏和交互应用。
Three.js:最流行的 3D 库,封装了 WebGL 细节。
这些库大大简化了复杂场景的开发,开发者可以根据需求选择合适的工具。
总结
Canvas 是 Web 图形开发的基石,从简单的矩形绘制到复杂的 3D 渲染,它提供了丰富的 API 和无限的扩展可能。本文从基础概念出发,涵盖了元素创建、上下文获取、基本绘图、样式、变换、动画、交互和性能优化,随后深入探讨了 WebGL、图像处理、物理引擎、数据可视化、游戏框架以及现代库的应用。掌握这些知识后,您将能够自信地使用 Canvas 构建各种创意项目,无论是数据可视化、小游戏还是图像处理工具。未来,随着 WebGPU 等新技术的出现,Canvas 的能力还将进一步扩展,值得持续关注和学习。
彩蛋
文中配有完整示例,点击《Canvas 基础示例》即可查看。