"想用代码在网页上自由绘制图形、制作炫酷动画,却不知从何下手?Canvas 听起来高大上,入门真的那么难吗?🤔 本文专为前端萌新打造!手把手、保姆级 带你系统入门 Canvas,从画一个方块开始,逐步解锁绘制、样式、动画等核心技能。最后,我们将一起用不到 100 行代码实现一个超有成就感的动态粒子背景效果! 告别枯燥理论,马上动手,让你的图形'动'起来吧!🚀"
-
1.1 Canvas 是什么?
- 简单比喻:
<canvas>
标签就像一块空白的"画布",JavaScript 就是你的"画笔"。 - 作用:用于在网页上动态渲染 图形、图表、游戏、动画等(强调动态,区别于静态图片)。
- 举个栗子:常见的图表库 (ECharts)、小游戏、动态背景、图像处理工具背后都可能用到 Canvas。
- 简单比喻:
-
1.2 准备工作:创建你的第一块画布
- HTML 中添加
<canvas>
标签,指定id
,width
,height
(强调必须设置宽高,否则默认很小)。 - JavaScript 中获取画布上下文 (
context
):const ctx = canvas.getContext('2d');
(目前主要学习 2D)。 - 代码示例 (基础结构):
- HTML 中添加
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>我的Canvas初体验</title>
<style>
canvas {
border: 1px solid #ccc; /* 加个边框方便看到画布范围 */
}
</style>
</head>
<body>
<canvas id="myCanvas" width="400" height="300"></canvas>
<script>
// 1. 获取Canvas元素
const canvas = document.getElementById("myCanvas");
// 2. 获取2D渲染上下文 - 这是我们的"画笔"
const ctx = canvas.getContext("2d");
// 接下来所有绘制操作都通过 ctx 进行!
</script>
</body>
</html>

二、 拿起"画笔":基础图形绘制 (核心API详解)
-
2.1 坐标系:定位你的图形
- 画布左上角是原点
(0, 0)
。 - X 轴向右递增,Y 轴向下递增 (与数学坐标系不同,这点特别提醒初学者!)。
- 图解: 清晰标注坐标系的示意图。
- 画布左上角是原点
-
2.2 绘制矩形:从方块开始
ctx.fillRect(x, y, width, height)
: 绘制填充矩形。ctx.strokeRect(x, y, width, height)
: 绘制描边矩形。ctx.clearRect(x, y, width, height)
: 清除矩形区域内容 (橡皮擦)。- 代码示例 & 效果:
js
// 在 (50, 50) 位置画一个宽100, 高80的红色填充矩形
ctx.fillStyle = 'red'; // 设置填充颜色
ctx.fillRect(50, 50, 100, 80);
// 在 (200, 100) 位置画一个宽150, 高60的蓝色描边矩形 (边框)
ctx.strokeStyle = 'blue'; // 设置描边颜色
ctx.lineWidth = 3; // 设置描边宽度
ctx.strokeRect(200, 100, 150, 60);
// 清除 (100, 150) 开始宽50, 高30的区域 (会露出画布底色,通常是透明或白色)
ctx.clearRect(100, 150, 50, 30);
2.3 绘制路径:线条与自由形状
-
核心步骤 (
beginPath()
->moveTo()
->lineTo()
/arc()
等 ->stroke()
/fill()
):-
ctx.beginPath()
: 开始一条新路径 (重要!避免路径连续)。 -
ctx.moveTo(x, y)
: 将"画笔"移动到起点,不画线。 -
ctx.lineTo(x, y)
: 从当前点画直线到目标点。 -
ctx.arc(x, y, radius, startAngle, endAngle [, anticlockwise])
: 画圆弧/圆。x, y
: 圆心。radius
: 半径。startAngle
,endAngle
: 开始和结束弧度 (0 在 3点钟方向,Math.PI
是180度)。anticlockwise
: 可选,true
为逆时针,默认false
(顺时针)。
-
ctx.closePath()
: (可选) 将路径的起点和终点连接起来形成闭合图形。 -
ctx.stroke()
: 描边路径。 -
ctx.fill()
: 填充 闭合路径 (自动closePath
效果)。
-
-
代码示例 & 效果 (画三角形和笑脸):
js
// 画一个绿色描边的三角形
ctx.beginPath();
ctx.moveTo(100, 50); // 起点 A
ctx.lineTo(50, 150); // 画线到 B
ctx.lineTo(150, 150); // 画线到 C
ctx.closePath(); // 连接 C 回 A (形成闭合)
ctx.strokeStyle = 'green';
ctx.lineWidth = 2;
ctx.stroke();
// 画一个黄色填充的笑脸
ctx.beginPath();
ctx.arc(300, 100, 50, 0, Math.PI * 2); // 脸 (整圆)
ctx.moveTo(280, 90); // 移动画笔到左眼起点 (避免画连线)
ctx.arc(280, 90, 8, 0, Math.PI * 2); // 左眼
ctx.moveTo(320, 90); // 移动画笔到右眼起点
ctx.arc(320, 90, 8, 0, Math.PI * 2); // 右眼
// 画微笑嘴 (半圆弧)
ctx.moveTo(320, 120); // 稍微调整起点位置 (可选)
ctx.arc(300, 120, 25, 0, Math.PI, false); // 从0弧度(3点)到PI弧度(9点),顺时针画下半圆
ctx.fillStyle = 'yellow';
ctx.fill(); // 填充脸和眼睛
ctx.strokeStyle = 'black';
ctx.lineWidth = 1;
ctx.stroke(); // 描边所有路径 (脸、眼睛轮廓、嘴)
2.4 赋予色彩与样式
ctx.fillStyle = color
: 设置填充 颜色/渐变/图案。 (color
可以是'red'
,'#FF0000'
,'rgba(255,0,0,0.5)'
等)。ctx.strokeStyle = color
: 设置描边颜色/渐变/图案。ctx.lineWidth = value
: 设置描边宽度 (数字)。ctx.lineCap = type
: 设置线段端点 样式 ('butt'
,'round'
,'square'
)。ctx.lineJoin = type
: 设置线段连接处 样式 ('miter'
,'round'
,'bevel'
)。- 代码示例 & 效果 (不同样式线条):
js
ctx.beginPath();
ctx.moveTo(50, 30);
ctx.lineTo(200, 30);
ctx.lineWidth = 10;
ctx.lineCap = 'butt'; // 默认平头
ctx.strokeStyle = 'blue';
ctx.stroke();
ctx.beginPath();
ctx.moveTo(50, 70);
ctx.lineTo(200, 70);
ctx.lineWidth = 10;
ctx.lineCap = 'round'; // 圆头
ctx.strokeStyle = 'green';
ctx.stroke();
ctx.beginPath();
ctx.moveTo(50, 110);
ctx.lineTo(200, 110);
ctx.lineWidth = 10;
ctx.lineCap = 'square'; // 方头 (比butt两端各长出线宽的一半)
ctx.strokeStyle = 'red';
ctx.stroke();
三、 让画面"活"起来:动画基础
-
3.1 动画原理:擦除 -> 更新 -> 重绘
-
核心思想:连续快速地在画布上绘制一系列略有不同的帧,形成视觉上的动画。
-
关键步骤:
- 清除画布 (
ctx.clearRect(0, 0, canvas.width, canvas.height)
): 擦掉上一帧的内容。 - 更新状态: 改变需要运动的物体的位置、大小、颜色、角度等属性。
- 重绘图形: 根据更新后的状态,重新绘制所有图形。
- 循环: 使用
requestAnimationFrame(callback)
方法,在浏览器下一次重绘之前再次调用步骤 1-3 的函数 (callback
),形成流畅循环。
- 清除画布 (
-
-
3.2
requestAnimationFrame
:你的动画循环引擎- 浏览器原生提供的API,专门为动画设计。
- 比
setInterval
或setTimeout
更高效、更流畅(与浏览器刷新率同步)。 - 基本用法:
js
function animate() {
// 1. 清除画布
ctx.clearRect(0, 0, canvas.width, canvas.height);
// 2. 更新状态 (例如:小球的位置 x += speedX)
// 3. 重绘图形 (用更新后的状态画小球)
drawBall();
// 4. 请求下一帧
requestAnimationFrame(animate);
}
// 启动动画
animate();
3.3 简单动画示例:弹跳的小球
- 代码示例 & 效果:
js
let x = 100; // 小球初始x坐标
let y = 100; // 小球初始y坐标
let radius = 20; // 小球半径
let speedX = 2; // x方向速度
let speedY = 3; // y方向速度
function drawBall() {
ctx.beginPath();
ctx.arc(x, y, radius, 0, Math.PI * 2);
ctx.fillStyle = 'purple';
ctx.fill();
ctx.strokeStyle = 'black';
ctx.stroke();
}
function animate() {
// 清除整个画布
ctx.clearRect(0, 0, canvas.width, canvas.height);
// 绘制小球
drawBall();
// 更新位置
x += speedX;
y += speedY;
// 边界检测 (碰到左右壁反弹)
if (x + radius > canvas.width || x - radius < 0) {
speedX = -speedX; // 反转X速度
}
// 边界检测 (碰到上下壁反弹)
if (y + radius > canvas.height || y - radius < 0) {
speedY = -speedY; // 反转Y速度
}
// 请求下一帧
requestAnimationFrame(animate);
}
animate(); // 启动动画!

四、 实战演练:打造炫酷粒子动画背景 (成就感爆棚!)
-
4.1 项目目标: 创建一个由大量彩色小点(粒子)组成的背景,粒子在画布中随机移动,当它们靠近时用线连接起来,形成流动的网状效果。
-
4.2 核心思路:
-
粒子 (Particle) 对象:
- 属性:当前位置 (
x
,y
)、速度 (vx
,vy
)、半径 (radius
)、颜色 (color
)。 - 方法:
draw()
绘制自身,update()
更新位置和边界检测。
- 属性:当前位置 (
-
粒子数组: 创建多个
Particle
实例,放入数组particles = []
。 -
动画循环:
- 清除画布。
- 遍历所有粒子:调用
update()
更新位置,调用draw()
绘制粒子。 - 连接粒子: 遍历粒子数组,计算每两个粒子之间的距离。如果距离小于某个阈值,就在它们之间画一条半透明的线。距离越近,线越不透明/越粗。
-
-
4.3 完整代码实现 (带详细注释):
js
const canvas = document.getElementById('myCanvas');
const ctx = canvas.getContext('2d');
// 设置画布大小为窗口大小
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
// 粒子数组
const particles = [];
// 粒子数量 (可根据性能调整)
const particleCount = 80;
// 连接粒子的最大距离阈值
const connectDistance = 100;
// 1. 定义粒子类 (Particle)
class Particle {
constructor() {
// 随机初始位置 (在整个画布范围内)
this.x = Math.random() * canvas.width;
this.y = Math.random() * canvas.height;
// 随机速度 (-1 到 1 之间)
this.vx = (Math.random() - 0.5) * 2;
this.vy = (Math.random() - 0.5) * 2;
// 固定或随机半径 (例如 2-5像素)
this.radius = Math.random() * 3 + 2;
// 随机颜色 (这里用HSLA更易生成鲜艳颜色)
this.color = `hsla(${Math.random() * 360}, 70%, 50%, 0.8)`;
}
// 绘制粒子
draw() {
ctx.beginPath();
ctx.arc(this.x, this.y, this.radius, 0, Math.PI * 2);
ctx.fillStyle = this.color;
ctx.fill();
}
// 更新粒子位置
update() {
// 移动
this.x += this.vx;
this.y += this.vy;
// 边界检查 (碰壁反弹)
if (this.x <= this.radius || this.x >= canvas.width - this.radius) {
this.vx = -this.vx;
}
if (this.y <= this.radius || this.y >= canvas.height - this.radius) {
this.vy = -this.vy;
}
}
}
// 2. 初始化粒子数组
function init() {
for (let i = 0; i < particleCount; i++) {
particles.push(new Particle());
}
}
// 3. 连接粒子函数 (计算距离并画线)
function connectParticles() {
for (let i = 0; i < particles.length; i++) {
for (let j = i + 1; j < particles.length; j++) {
const p1 = particles[i];
const p2 = particles[j];
// 计算两个粒子之间的距离
const dx = p1.x - p2.x;
const dy = p1.y - p2.y;
const distance = Math.sqrt(dx * dx + dy * dy);
// 如果距离小于阈值,则画一条连接线
if (distance < connectDistance) {
// 根据距离计算透明度/宽度 (距离越小,线越明显)
const opacity = 1 - distance / connectDistance; // 0到1
ctx.beginPath();
ctx.moveTo(p1.x, p1.y);
ctx.lineTo(p2.x, p2.y);
ctx.strokeStyle = `rgba(200, 200, 200, ${opacity * 0.4})`; // 半透明灰色线
ctx.lineWidth = opacity * 1.5; // 线宽随距离变化
ctx.stroke();
}
}
}
}
// 4. 动画循环函数
function animate() {
// 清除画布 (用半透明黑色覆盖实现拖尾效果,注释掉则无拖尾)
ctx.fillStyle = 'rgba(0, 0, 0, 0.05)';
ctx.fillRect(0, 0, canvas.width, canvas.height);
// 更新并绘制所有粒子
particles.forEach(particle => {
particle.update();
particle.draw();
});
// 连接粒子
connectParticles();
// 请求下一帧
requestAnimationFrame(animate);
}
// 启动程序!
init(); // 创建粒子
animate(); // 开始动画
// 可选:窗口大小改变时重置画布和粒子 (简单处理:刷新页面)
window.addEventListener('resize', () => {
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
// 更完善的做法是重新初始化粒子或调整粒子位置
});
-
4.4 效果展示与调参:
-
强烈建议: 在文章中嵌入这个粒子动画的实际运行效果! (可以用 Codepen 嵌入 或 录制成GIF/视频)。视觉效果是最大的吸引点。
-
调参提示:
-
修改
particleCount
:粒子数量 (注意性能)。 -
修改
connectDistance
:连接距离阈值。 -
修改
ctx.fillStyle = 'rgba(0, 0, 0, 0.05)';
中的透明度 (0.05
):值越小拖尾越长越淡;改为ctx.clearRect(0, 0, canvas.width, canvas.height);
则无拖尾效果。 -
修改粒子颜色生成逻辑 (
hsla
):产生不同色调。 -
修改连接线的颜色和透明度 (
rgba(200, 200, 200, ${opacity * 0.4})
)。
-
-

五、 总结与进阶方向
-
5.1 本文总结:
- 掌握了 Canvas 的核心概念:画布、上下文、坐标系。
- 学会了绘制基础图形:矩形、路径(直线、圆弧)、设置样式。
- 理解了动画原理和
requestAnimationFrame
的使用。 - 成功实现了一个视觉效果不错的粒子动画系统!
-
5.2 你能用Canvas做什么?
- 数据可视化图表。
- 小游戏 (如贪吃蛇、Flappy Bird)。
- 独特的动态网页背景/特效。
- 简单的图像处理/滤镜。
- 交互式绘图工具。
-
5.3 下一步学习建议:
- 深入路径操作:
bezierCurveTo
(贝塞尔曲线),quadraticCurveTo
(二次曲线)。 - 文本绘制:
fillText()
,strokeText()
, 字体样式设置。 - 图像操作:
drawImage()
绘制图片,像素操作 (getImageData
,putImageData
)。 - 变换:
translate()
,rotate()
,scale()
,save()
,restore()
(非常重要!)。 - 渐变与图案:
createLinearGradient()
,createRadialGradient()
,createPattern()
。 - 性能优化: 离屏Canvas、避免过度绘制、合理使用
clearRect
。 - 探索 WebGL: 如果对3D图形或更复杂的图形计算感兴趣,可以学习Three.js或原生WebGL。
- 深入路径操作: