HTML5 Canvas 完整教程
目录
什么是 Canvas?
<canvas> 是 HTML5 引入的一个强大的元素,它提供了一个可以通过 JavaScript 进行绘制的位图画布,你可以把它想象成一块在浏览器中的“画布”,你拥有全套的“画笔”和“颜料”(即 JavaScript API),可以在上面绘制任何你想要的东西,从简单的图形到复杂的游戏、数据可视化和图像处理。

核心特点:
- 基于像素:Canvas 是基于像素的,这意味着当你放大图形时,它会变得模糊(位图特性)。
- 即时模式:Canvas 没有持久的对象,你绘制一个矩形,它就变成了像素,如果你想移动它,你必须清除画布,然后在新的位置重新绘制整个场景。
- 性能卓越:非常适合绘制大量图形、动画和游戏,因为 GPU 可以对它进行硬件加速。
入门:创建你的第一个 Canvas
使用 Canvas 非常简单,只需要在 HTML 中放置一个 HTML 结构 JavaScript 绘制
创建一个 Canvas 提供了三种直接绘制矩形的方法: 路径是绘制自定义形状的基础,它像一个“绘图计划”,告诉 Canvas 你想画什么,然后你决定是描边它还是填充它。 基本步骤: 示例:绘制一个三角形 使用 示例:绘制一个圆 Canvas 也可以绘制文本。 文本样式属性: 示例: 你可以将图像(如 示例: 几乎所有形状都有两个版本:填充和描边。 示例:线性渐变 变换允许你移动、旋转和缩放整个画布坐标系。重要提示:变换会影响之后的所有绘制操作。 使用变换的最佳实践:保存和恢复状态
变换会累积,这通常不是你想要的,为了避免混乱,可以使用 示例:绘制一个旋转的矩形 Canvas 动画的原理是:清除 -> 更新 -> 绘制 -> 重复。 语法:
目标: 让一个球从左边弹跳到右边。 你可以直接获取和修改画布上的像素数据。 示例:灰度滤镜 粒子系统是 Canvas 动画中的一个经典应用,我们将创建一个简单的粒子爆炸效果。 HTML: (与之前相同)
JavaScript ( 效果说明:
HTML5 Canvas 是一个极其强大和灵活的工具,掌握它的关键在于: 推荐资源: 希望这份教程能帮助你顺利入门并精通 HTML5 Canvas!祝你编码愉快!<canvas>
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">Canvas 教程</title>
<style>
body {
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
margin: 0;
background-color: #f0f0f0;
}
canvas {
border: 1px solid black;
}
</style>
</head>
<body>
<canvas id="myCanvas" width="500" height="400"></canvas>
<script src="main.js"></script>
</body>
</html>
<canvas> 元素有两个重要的属性:width 和 height,它们定义了画布的分辨率(以像素为单位)。注意:不要用 CSS 来设置画布的宽高,CSS 只会拉伸画布,导致内容模糊。main.js 文件,开始绘制。// 1. 获取 canvas 元素
const canvas = document.getElementById('myCanvas');
// 2. 获取 2D 绘图上下文
const ctx = canvas.getContext('2d');
// 3. 在画布上绘制一个矩形
ctx.fillStyle = 'green'; // 设置填充颜色
ctx.fillRect(10, 10, 150, 100); // 绘制一个矩形 (x, y, width, height)
// 4. 绘制一个描边矩形
ctx.strokeStyle = 'blue';
ctx.lineWidth = 5;
ctx.strokeRect(200, 10, 150, 100);
// 5. 绘制一条线
ctx.beginPath();
ctx.moveTo(10, 150); // 移动到起点
ctx.lineTo(400, 150); // 画一条线到终点
ctx.strokeStyle = 'red';
ctx.lineWidth = 2;
ctx.stroke();
核心概念:绘图上下文
getContext('2d') 是 Canvas 的核心,它返回一个 CanvasRenderingContext2D 对象,这个对象包含了所有用于在画布上绘制的 API 和属性,你所有的绘图操作都是通过这个 ctx 对象来完成的。
绘制基本图形
矩形
ctx.fillRect(x, y, width, height): 绘制一个填充的矩形。ctx.strokeRect(x, y, width, height): 绘制一个描边的矩形。ctx.clearRect(x, y, width, height): 清除指定矩形区域内的像素,使其变为透明。路径 - 绘制复杂图形的关键
ctx.beginPath(): 开始一条新路径。非常重要!这会重置当前的路径。ctx.moveTo(x, y): 将画笔移动到一个指定的点,但不画线。ctx.lineTo(x, y): 从当前点画一条直线到指定的新点。ctx.closePath(): 自动从当前点画一条线回到路径的起点。ctx.stroke(): 描边(绘制)当前路径。ctx.fill(): 填充当前路径。
const canvas = document.getElementById('myCanvas');
const ctx = canvas.getContext('2d');
ctx.beginPath();
ctx.moveTo(50, 50); // 移动到点 A
ctx.lineTo(150, 50); // 画线到点 B
ctx.lineTo(100, 150); // 画线到点 C
ctx.closePath(); // 闭合路径 (从 C 回到 A)
ctx.fillStyle = 'purple';
ctx.fill();
圆形和弧线
arc() 方法来绘制弧线或圆形。
ctx.arc(x, y, radius, startAngle, endAngle, anticlockwise)
x, y: 圆心坐标。radius: 半径。startAngle, endAngle: 起始和结束角度(弧度制,不是角度!)。0 弧度是 3点钟方向,Math.PI * 2 是一个完整的圆。anticlockwise: true 表示逆时针,false 表示顺时针(默认)。const canvas = document.getElementById('myCanvas');
const ctx = canvas.getContext('2d');
ctx.beginPath();
// 圆心(250, 200), 半径50, 从0到Math.PI*2(一个完整的圆)
ctx.arc(250, 200, 50, 0, Math.PI * 2);
ctx.fillStyle = 'orange';
ctx.fill();
文本
ctx.fillText(text, x, y, [maxWidth]): 绘制填充文本。ctx.strokeText(text, x, y, [maxWidth]): 绘制描边文本。
ctx.font: 设置字体,格式与 CSS font 相同 (e.g., "20px Arial")。ctx.fillStyle / ctx.strokeStyle: 设置文本颜色。ctx.textAlign: 文本对齐方式 (left, center, right)。ctx.textBaseline: 文本基线 (top, middle, bottom, alphabetic)。const canvas = document.getElementById('myCanvas');
const ctx = canvas.getContext('2d');
ctx.font = '30px Arial';
ctx.fillStyle = 'black';
ctx.textAlign = 'center';
ctx.fillText('Hello Canvas!', canvas.width / 2, canvas.height / 2);
图像
<img> 标签或图片 URL)绘制到画布上。
ctx.drawImage(image, x, y, [width, height])const canvas = document.getElementById('myCanvas');
const ctx = canvas.getContext('2d');
const img = new Image();
img.src = 'path/to/your/image.png';
// 等待图片加载完成
img.onload = function() {
ctx.drawImage(img, 50, 50, 200, 150); // 绘制图片,并指定宽高
};
样式与颜色
填充与描边
ctx.fill(): 使用 ctx.fillStyle 设置的颜色填充路径。ctx.stroke(): 使用 ctx.strokeStyle 设置的颜色和 ctx.lineWidth 设置的宽度来描边路径。颜色、透明度和渐变
ctx.fillStyle = 'red'; // 颜色名ctx.fillStyle = '#FF0000'; // 十六进制ctx.fillStyle = 'rgb(255, 0, 0)'; // RGBctx.fillStyle = 'rgba(255, 0, 0, 0.5)'; // RGBA (最后一个值是透明度 0-1)
createLinearGradient(x0, y0, x1, y1): 创建线性渐变。createRadialGradient(x0, y0, r0, x1, y1, r1): 创建径向渐变。gradient.addColorStop(position, color): 添加颜色断点。position 在 0 到 1 之间。const canvas = document.getElementById('myCanvas');
const ctx = canvas.getContext('2d');
const gradient = ctx.createLinearGradient(0, 0, canvas.width, 0);
gradient.addColorStop(0, 'blue');
gradient.addColorStop(1, 'green');
ctx.fillStyle = gradient;
ctx.fillRect(0, 0, canvas.width, canvas.height);
阴影
ctx.shadowColor = 'rgba(0, 0, 0, 0.5)'; // 阴影颜色ctx.shadowBlur = 10; // 阴影模糊程度ctx.shadowOffsetX = 5; // 阴影 X 轴偏移ctx.shadowOffsetY = 5; // 阴影 Y 轴偏移线条样式
ctx.lineWidth = 5; // 线条宽度ctx.lineCap = 'round'; // 线条末端样式 (butt, round, square)ctx.lineJoin = 'miter'; // 线条连接处样式 (miter, round, bevel)ctx.setLineDash([5, 15]); // 设置虚线,数组 [实线长度, 空白长度]
变换
平移
ctx.translate(x, y): 将画布的原点 (0, 0) 移动到 (x, y)。旋转
ctx.rotate(angle): 围绕当前原点旋转画布。angle 是弧度。缩放
ctx.scale(x, y): 缩放画布。x 和 y 是缩放因子。save() 和 restore()。
ctx.save(): 保存当前画布的状态(包括所有样式、变换等)到一个栈中。ctx.restore(): 从栈中恢复最近保存的状态。const canvas = document.getElementById('myCanvas');
const ctx = canvas.getContext('2d');
// 绘制一个原始矩形
ctx.fillStyle = 'blue';
ctx.fillRect(50, 50, 100, 100);
// 保存当前状态
ctx.save();
// 应用变换
ctx.translate(150, 150); // 将原点移动到矩形中心
ctx.rotate(Math.PI / 4); // 旋转45度 (Math.PI / 4 弧度)
// 绘制一个变换后的矩形
// 因为坐标系已经移动和旋转,所以坐标是相对于新原点的
ctx.fillStyle = 'red';
ctx.fillRect(-50, -50, 100, 100); // 矩形中心在(0,0)
// 恢复之前保存的状态
ctx.restore();
动画基础
使用
requestAnimationFramerequestAnimationFrame 是现代浏览器提供的用于制作动画的 API,它比 setInterval 或 setTimeout 更高效,因为它会浏览器的重绘周期同步,从而节省资源并使动画更流畅。requestAnimationFrame(callback)
callback 是一个函数,浏览器在下一次重绘之前会调用它。动画循环示例
const canvas = document.getElementById('myCanvas');
const ctx = canvas.getContext('2d');
// 1. 定义对象状态
let x = 0;
let y = canvas.height / 2;
let dx = 2; // x轴速度
let radius = 20;
function drawBall() {
ctx.beginPath();
ctx.arc(x, y, radius, 0, Math.PI * 2);
ctx.fillStyle = '#0095DD';
ctx.fill();
ctx.closePath();
}
function update() {
// 2. 更新状态
x += dx;
// 边界检测
if (x + radius > canvas.width || x - radius < 0) {
dx = -dx; // 反转方向
}
}
function animate() {
// 3. 清除画布
ctx.clearRect(0, 0, canvas.width, canvas.height);
// 4. 更新和绘制
update();
drawBall();
// 5. 请求下一帧
requestAnimationFrame(animate);
}
// 启动动画
animate();
高级主题
像素操作
ctx.getImageData(x, y, width, height): 获取一个矩形区域的像素数据,返回一个 ImageData 对象。ctx.putImageData(imageData, x, y): 将 ImageData 对象放回画布。ImageData.data: 一个一维数组,包含 [R, G, B, A, R, G, B, A, ...] 值。// ... (获取 canvas 和 ctx)
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
const data = imageData.data;
for (let i = 0; i < data.length; i += 4) {
// 获取 RGB 值
const r = data[i];
const g = data[i + 1];
const b = data[i + 2];
// 计算灰度值 (加权平均)
const avg = 0.299 * r + 0.587 * g + 0.114 * b;
// 将 RGB 都设置为灰度值
data[i] = avg; // R
data[i + 1] = avg; // G
data[i + 2] = avg; // B
}
// 将处理后的数据放回画布
ctx.putImageData(imageData, 0, 0);
合成与剪辑
globalCompositeOperation 属性设置,source-over (默认), multiply, screen 等。clip() 方法实现,它需要一个路径作为剪切区域。
实战案例:粒子系统
particles.js):const canvas = document.getElementById('myCanvas');
const ctx = canvas.getContext('2d');
// 1. 粒子类
class Particle {
constructor(x, y) {
this.x = x;
this.y = y;
this.size = Math.random() * 5 + 1;
this.speedX = Math.random() * 3 - 1.5; // -1.5 到 1.5
this.speedY = Math.random() * 3 - 1.5;
this.color = `hsl(${Math.random() * 360}, 50%, 50%)`;
}
update() {
this.x += this.speedX;
this.y += this.speedY;
// 粒子逐渐变小
if (this.size > 0.2) this.size -= 0.05;
}
draw() {
ctx.fillStyle = this.color;
ctx.beginPath();
ctx.arc(this.x, this.y, this.size, 0, Math.PI * 2);
ctx.fill();
}
}
// 2. 粒子数组
let particlesArray = [];
// 3. 鼠标移动事件监听
let mouseX = 0;
let mouseY = 0;
canvas.addEventListener('mousemove', (event) => {
const rect = canvas.getBoundingClientRect();
mouseX = event.clientX - rect.left;
mouseY = event.clientY - rect.top;
// 在鼠标位置创建新粒子
for (let i = 0; i < 5; i++) {
particlesArray.push(new Particle(mouseX, mouseY));
}
});
// 4. 动画循环
function animate() {
// 使用半透明的黑色填充画布,实现拖尾效果
ctx.fillStyle = 'rgba(0, 0, 0, 0.05)';
ctx.fillRect(0, 0, canvas.width, canvas.height);
// 更新和绘制所有粒子
for (let i = 0; i < particlesArray.length; i++) {
particlesArray[i].update();
particlesArray[i].draw();
// 移除太小的粒子
if (particlesArray[i].size <= 0.2) {
particlesArray.splice(i, 1);
i--;
}
}
requestAnimationFrame(animate);
}
// 启动动画
animate();
rgba(0, 0, 0, 0.05),而不是完全不透明的黑色,所以会产生粒子运动的拖尾效果。
总结与资源
ctx 对象。save() 和 restore() 来管理状态。requestAnimationFrame 是你的好朋友。
