Canvas 完全指南:从基础绘图到高性能动画
在网页上绘制图形、创建动画、实现图像处理——这些需求在传统的 Web 开发中一直存在,但直到 HTML5 带来了<canvas>元素,才真正让浏览器拥有了“画布”能力。Canvas 是一个位图绘图 API,通过 JavaScript 在网页上绘制 2D 图形、图像和动画,是游戏开发、数据可视化、图像编辑、动态图表等场景的核心技术。
本文将从零开始,系统讲解 Canvas 的核心 API、绘图原理、动画机制以及性能优化技巧,带你从入门到精通。
一、Canvas 是什么?
1.1 定义与定位
Canvas 是 HTML5 引入的一个用于动态绘制图形的元素,它本身只是一个“容器”,真正的绘图工作通过 JavaScript 调用其上下文(Context)API 来完成。与 SVG 不同,Canvas 是位图(像素)级别的绘图,适合频繁更新和复杂渲染的场景。
1.2 Canvas vs SVG
对比维度 | Canvas | SVG |
图形类型 | 位图(像素级) | 矢量图(保留对象) |
性能 | 大量图形时性能好 | 大量图形时 DOM 节点多,性能下降 |
交互性 | 需手动计算点击位置 | 原生支持事件绑定 |
放大 | 会模糊 | 不失真 |
适用场景 | 游戏、动画、图像处理 | 图表、图标、静态图形 |
一句话选择:需要频繁重绘和大规模图形用 Canvas;需要交互和缩放用 SVG。
二、快速上手:第一个 Canvas 程序
2.1 基本结构
<!DOCTYPE html> <html> <head> <style> /* 可选:给画布加边框以便看清区域 */ canvas { border: 1px solid #ccc; } </style> </head> <body> <!-- 1. 在 HTML 中放置 canvas 元素 --> <canvas id="myCanvas" width="600" height="400"></canvas> <script> // 2. 获取画布元素 const canvas = document.getElementById('myCanvas'); // 3. 获取绘图上下文(2D) const ctx = canvas.getContext('2d'); // 4. 开始绘图 ctx.fillStyle = 'skyblue'; ctx.fillRect(50, 50, 200, 100); ctx.fillStyle = 'orange'; ctx.fillRect(300, 50, 150, 150); </script> </body> </html>2.2 核心三要素
<canvas>元素:HTML 中的画布容器,需设置width和height(注意:CSS 尺寸会缩放画布,而width/height属性定义的是绘图分辨率)。getContext('2d'):获取 2D 绘图上下文,所有绘图操作都通过它完成。- 绘图 API:如
fillRect、strokeRect、fillStyle等。
⚠️关键提示:<canvas>的width和height属性与 CSS 尺寸是两回事。CSS 拉伸画布会导致图像模糊,建议始终用属性设置尺寸,CSS 只控制布局。
三、坐标系与基础形状
Canvas 的坐标系原点(0, 0)位于左上角,x轴向右为正,y轴向下为正。
3.1 矩形
// 填充矩形 ctx.fillStyle = '#ff0000'; ctx.fillRect(10, 10, 100, 50); // (x, y, width, height) // 描边矩形 ctx.strokeStyle = '#00ff00'; ctx.lineWidth = 3; ctx.strokeRect(130, 10, 100, 50); // 清除矩形区域(恢复为透明) ctx.clearRect(20, 20, 80, 30);3.2 路径绘制
Canvas 中大多数复杂图形都通过路径(Path)绘制:
javascript
ctx.beginPath(); // 开始路径 ctx.moveTo(50, 50); // 移动笔触到起点 ctx.lineTo(150, 50); // 画线到 (150, 50) ctx.lineTo(100, 120); // 画线到 (100, 120) ctx.closePath(); // 闭合路径(回到起点) ctx.fillStyle = 'purple'; ctx.fill(); // 填充 ctx.strokeStyle = 'black'; ctx.lineWidth = 2; ctx.stroke(); // 描边3.3 圆形与弧线
ctx.beginPath(); // arc(x, y, radius, startAngle, endAngle, counterclockwise) ctx.arc(200, 150, 80, 0, Math.PI * 2); // 整圆 ctx.fillStyle = 'gold'; ctx.fill(); ctx.stroke(); // 半圆弧 ctx.beginPath(); ctx.arc(400, 150, 80, 0, Math.PI); ctx.stroke();四、样式与状态管理
4.1 常用样式属性
属性 | 说明 | 示例 |
| 填充颜色/渐变/图案 |
, |
| 描边颜色 | 同上 |
| 线条宽度(px) |
|
| 线条端点样式 |
, |
| 线条拐角样式 |
, |
| 全局透明度(0~1) |
|
| 阴影颜色 |
|
| 阴影模糊程度 |
|
4.2 状态栈:save() 与 restore()
ctx.fillStyle = 'red'; ctx.save(); // 保存当前状态(红色) ctx.fillStyle = 'blue'; // 修改为蓝色 ctx.fillRect(10, 10, 50, 50); ctx.restore(); // 恢复到红色 ctx.fillRect(70, 10, 50, 50); // 红色矩形使用场景:在绘制复杂图形时,频繁使用save()和restore()避免样式污染。
4.3 渐变与图案
// 线性渐变 const gradient = ctx.createLinearGradient(0, 0, 200, 0); gradient.addColorStop(0, 'red'); gradient.addColorStop(0.5, 'yellow'); gradient.addColorStop(1, 'blue'); ctx.fillStyle = gradient; ctx.fillRect(10, 10, 200, 100); // 径向渐变 const radial = ctx.createRadialGradient(300, 60, 10, 300, 60, 80); radial.addColorStop(0, 'white'); radial.addColorStop(1, 'darkblue'); ctx.fillStyle = radial; ctx.beginPath(); ctx.arc(300, 60, 80, 0, Math.PI * 2); ctx.fill(); // 图案填充 const img = new Image(); img.src = 'pattern.png'; img.onload = function() { const pattern = ctx.createPattern(img, 'repeat'); ctx.fillStyle = pattern; ctx.fillRect(0, 150, 600, 100); };五、文本绘制
ctx.font = 'bold 30px Arial'; ctx.fillStyle = '#333'; ctx.textAlign = 'center'; // 'left', 'center', 'right' ctx.textBaseline = 'middle'; // 'top', 'middle', 'bottom' ctx.fillText('Hello Canvas', 300, 200); ctx.strokeStyle = '#ff0000'; ctx.lineWidth = 1; ctx.strokeText('描边文字', 300, 250); // 测量文字宽度 const metrics = ctx.measureText('Hello Canvas'); console.log(metrics.width);六、图像处理
6.1 绘制图像
const img = new Image(); img.src = 'photo.jpg'; img.onload = function() { // 绘制原图 ctx.drawImage(img, 0, 0); // 缩放绘制 ctx.drawImage(img, 200, 0, 100, 100); // 裁剪绘制 (sx, sy, sw, sh, dx, dy, dw, dh) ctx.drawImage(img, 50, 50, 100, 100, 300, 0, 100, 100); };6.2 像素操作
// 获取像素数据 const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height); const data = imageData.data; // Uint8ClampedArray [R,G,B,A, R,G,B,A, ...] // 转换为灰度图 for (let i = 0; i < data.length; i += 4) { const gray = data[i] * 0.3 + data[i+1] * 0.59 + data[i+2] * 0.11; data[i] = gray; data[i+1] = gray; data[i+2] = gray; } ctx.putImageData(imageData, 0, 0);七、变换与坐标系操作
7.1 平移、旋转、缩放
ctx.save(); ctx.translate(300, 200); // 将原点移到 (300, 200) ctx.rotate(Math.PI / 4); // 旋转 45 度 ctx.scale(1.5, 1.5); // 放大 1.5 倍 ctx.fillStyle = 'red'; ctx.fillRect(-50, -50, 100, 100); ctx.restore();注意:变换操作是累加的,建议在变换前save(),操作完成后restore()。
7.2 矩阵变换
ctx.setTransform(a, b, c, d, e, f); // 对应矩阵:[[a, c, e], [b, d, f], [0, 0, 1]]八、动画基础
Canvas 动画的核心是循环重绘,通过requestAnimationFrame实现平滑动画。
8.1 基本动画循环
let x = 0; let direction = 1; function animate() { // 清除画布 ctx.clearRect(0, 0, canvas.width, canvas.height); // 更新状态 x += 2 * direction; if (x > canvas.width - 50 || x < 0) { direction *= -1; } // 绘制 ctx.fillStyle = 'blue'; ctx.fillRect(x, 150, 50, 50); // 请求下一帧 requestAnimationFrame(animate); } animate();8.2 帧率控制与 deltaTime
let lastTime = 0; const speed = 100; // 像素/秒 function animate(timestamp) { const deltaTime = (timestamp - lastTime) / 1000; // 转为秒 lastTime = timestamp; x += speed * deltaTime; // ... 绘制 requestAnimationFrame(animate); } requestAnimationFrame(animate);使用deltaTime确保动画在不同刷新率的设备上速度一致。
九、交互与事件
Canvas 本身不提供“点击某个圆形”的事件,需要手动计算。
9.1 鼠标点击检测
const circles = [ { x: 100, y: 150, r: 40, color: 'red' }, { x: 300, y: 150, r: 40, color: 'blue' }, ]; function drawCircles() { circles.forEach(c => { ctx.beginPath(); ctx.arc(c.x, c.y, c.r, 0, Math.PI * 2); ctx.fillStyle = c.color; ctx.fill(); }); } canvas.addEventListener('click', function(e) { const rect = canvas.getBoundingClientRect(); const mouseX = e.clientX - rect.left; const mouseY = e.clientY - rect.top; // 从后往前遍历(上层优先) for (let i = circles.length - 1; i >= 0; i--) { const c = circles[i]; const dx = mouseX - c.x; const dy = mouseY - c.y; if (dx * dx + dy * dy < c.r * c.r) { alert('点击了 ' + c.color + ' 圆'); break; } } });提示:getBoundingClientRect()用于将鼠标坐标转换为 canvas 坐标系,需考虑 canvas 的 CSS 缩放。
十、性能优化
优化策略 | 说明 |
离屏渲染 | 在内存中创建不可见 Canvas 绘制复杂内容,再一次性绘制到主画布 |
减少绘制调用 | 合并在同一路径中的绘制,避免频繁 / |
使用 | 取代 / |
避免 频繁调用 | 除非必要,减少像素级操作 |
控制重绘区域 | 使用 裁剪或 局部清除 |
合理使用 | 提示浏览器优化( ) |
十一、总结速查表
知识点 | 关键 API |
获取上下文 |
|
矩形 |
, , |
路径 |
, , , , , |
圆弧 |
|
样式 |
, , , , |
状态 |
, |
文本 |
, , |
图像 |
, , |
变换 |
, , , |
动画 |
|
一句话记忆:Canvas 是浏览器的像素级画布——通过 JavaScript 获取 2D 上下文,用路径、样式和变换绘制图形,用requestAnimationFrame驱动动画,用手动坐标计算实现交互,用离屏渲染和减少绘制调用优化性能。
Canvas 是现代 Web 前端开发的核心技能之一。从本章的示例出发,尝试实现一个“画板”应用(支持鼠标绘制、颜色切换、清空),再挑战一个简单的“弹球”游戏,你会逐步感受到 Canvas 的强大。动画和游戏开发才是它真正的用武之地,祝你创作愉快。
