Canvas 完全指南:从基础绘图到高性能动画

📅 2026/7/6 8:53:52 👁️ 阅读次数 📝 编程学习
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 核心三要素

  1. <canvas>元素:HTML 中的画布容器,需设置widthheight(注意:CSS 尺寸会缩放画布,而width/height属性定义的是绘图分辨率)。
  2. getContext('2d'):获取 2D 绘图上下文,所有绘图操作都通过它完成。
  3. 绘图 API:如fillRectstrokeRectfillStyle等。

⚠️关键提示<canvas>widthheight属性与 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 常用样式属性

属性

说明

示例

fillStyle

填充颜色/渐变/图案

'#ff0000'

,'rgba(255,0,0,0.5)'

strokeStyle

描边颜色

同上

lineWidth

线条宽度(px)

3

lineCap

线条端点样式

'round'

,'square'

lineJoin

线条拐角样式

'round'

,'bevel'

globalAlpha

全局透明度(0~1)

0.5

shadowColor

阴影颜色

'rgba(0,0,0,0.3)'

shadowBlur

阴影模糊程度

10

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 绘制复杂内容,再一次性绘制到主画布

减少绘制调用

合并在同一路径中的绘制,避免频繁beginPath()

/stroke()

使用requestAnimationFrame

取代setInterval

/setTimeout

避免getImageData

频繁调用

除非必要,减少像素级操作

控制重绘区域

使用drawImage

裁剪或clearRect

局部清除

合理使用will-change

提示浏览器优化(canvas.style.willChange = 'transform'


十一、总结速查表

知识点

关键 API

获取上下文

canvas.getContext('2d')

矩形

fillRect(x,y,w,h)

,strokeRect()

,clearRect()

路径

beginPath()

,moveTo()

,lineTo()

,closePath()

,fill()

,stroke()

圆弧

arc(x,y,r,startAngle,endAngle)

样式

fillStyle

,strokeStyle

,lineWidth

,globalAlpha

,shadow*

状态

save()

,restore()

文本

fillText()

,strokeText()

,measureText()

图像

drawImage()

,getImageData()

,putImageData()

变换

translate()

,rotate()

,scale()

,setTransform()

动画

requestAnimationFrame(callback)


一句话记忆:Canvas 是浏览器的像素级画布——通过 JavaScript 获取 2D 上下文,用路径、样式和变换绘制图形,用requestAnimationFrame驱动动画,用手动坐标计算实现交互,用离屏渲染和减少绘制调用优化性能。

Canvas 是现代 Web 前端开发的核心技能之一。从本章的示例出发,尝试实现一个“画板”应用(支持鼠标绘制、颜色切换、清空),再挑战一个简单的“弹球”游戏,你会逐步感受到 Canvas 的强大。动画和游戏开发才是它真正的用武之地,祝你创作愉快。