Canvas 绘图初体验:用代码在网页上"画画"的魔法
如果你写过几年前端,大概率对 DOM 操作、样式布局、组件封装这些事已经驾轻就熟。但有没有想过------浏览器其实还藏着一块"画板" ,你可以像画家一样,用代码一笔一笔地把图形画上去?
这就是 HTML5 的 Canvas。
今天这篇文章,我想带你从零开始,亲手在网页上"画"出第一个图形,感受一下用代码作画的奇妙体验。
一、Canvas 是什么?
简单来说,Canvas 是 HTML5 提供的一个画布元素,你可以在上面用 JavaScript 绘制图形、文字、图片,甚至做出动画和游戏。
它和 SVG 最大的区别在于:
| Canvas | SVG | |
|---|---|---|
| 绘制方式 | 逐像素渲染(位图) | 矢量描述(DOM 节点) |
| 适合场景 | 游戏、数据可视化、图像处理 | 图标、简单交互图形 |
| 缩放表现 | 放大可能模糊 | 无损缩放 |
| 交互 | 需要手动计算坐标 | 原生支持事件 |
一句话概括:Canvas 是画笔画出来的,SVG 是用文字描述出来的。
二、准备工作:三行代码搭好画板
先来一个最简单的 HTML 结构:
xml
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>Canvas 初体验</title>
<style>
body {
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
margin: 0;
background: #1a1a2e;
}
canvas {
border: 2px solid #e2e2e2;
border-radius: 8px;
}
</style>
</head>
<body>
<canvas id="myCanvas" width="600" height="400"></canvas>
<script src="main.js"></script>
</body>
</html>
关键点就两个:
<canvas>标签本身只是个容器,真正画画靠 JSwidth和height建议直接写在标签属性上(而不是 CSS 里),否则画面会被拉伸
三、拿到画笔:获取绘图上下文
在 main.js 里,第一步永远是获取 绘图上下文(context) :
ini
const canvas = document.getElementById('myCanvas');
const ctx = canvas.getContext('2d');
getContext('2d') 返回的是一个 CanvasRenderingContext2D 对象------你可以把它理解为画笔 + 调色盘 + 尺子的集合体,后面所有的绘制操作都靠它。
四、画一条线:你的第一行"绘画代码"
来画一条从画布左上角到右下角的对角线:
ini
// 1. 把笔移到起点
ctx.moveTo(50, 50);
// 2. 画到终点
ctx.lineTo(550, 350);
// 3. 设置线条颜色
ctx.strokeStyle = '#00d4ff';
// 4. 设置线条粗细
ctx.lineWidth = 3;
// 5. 描边(真正画出来)
ctx.stroke();
刷新页面,你应该能看到一条亮蓝色的斜线横跨画布 ✨
五个步骤,缺一不可:
scss
moveTo → lineTo → strokeStyle → lineWidth → stroke()
这就像真实画画一样:先确定起点,再确定终点,选好颜色,调好笔刷粗细,最后落笔。
五、画一个矩形:fill 和 stroke 的区别
Canvas 里有两种"画"法:
- stroke(描边) :只画轮廓线
- fill(填充) :把形状内部填满
ini
// 描边矩形
ctx.strokeStyle = '#ff6b6b';
ctx.lineWidth = 2;
ctx.strokeRect(100, 80, 150, 100);
// 填充矩形
ctx.fillStyle = 'rgba(78, 205, 196, 0.7)';
ctx.fillRect(300, 80, 150, 100);
strokeRect(x, y, width, height) 和 fillRect(x, y, width, height) 是快捷方法,不需要再调用 stroke() 或 fill()。
六、画一个圆:arc 方法
圆在 Canvas 里用 arc(圆弧)来画:
ini
ctx.beginPath();
ctx.arc(300, 250, 60, 0, Math.PI * 2);
ctx.fillStyle = '#f7dc6f';
ctx.fill();
ctx.strokeStyle = '#f39c12';
ctx.lineWidth = 3;
ctx.stroke();
arc(x, y, radius, startAngle, endAngle) 的参数含义:
| 参数 | 含义 |
|---|---|
| x, y | 圆心坐标 |
| radius | 半径 |
| startAngle | 起始角度(弧度) |
| endAngle | 结束角度(弧度) |
💡 注意 :角度用弧度 而不是度数。
Math.PI * 2就是一个完整的圆(360°)。
还有一点很重要:每次画新图形前,记得调用 beginPath() ,否则新旧路径会混在一起,描边时会全部重画一遍。
七、写一段文字
Canvas 也能写字:
ini
ctx.font = 'bold 28px "Microsoft YaHei", sans-serif';
ctx.fillStyle = '#ffffff';
ctx.textAlign = 'center';
ctx.fillText('Hello Canvas!', 300, 200);
fillText(text, x, y) 在指定位置绘制填充文字。对应的还有 strokeText() 只画文字轮廓。
八、来点动效:让圆动起来
静态的画布已经很有趣了,但 Canvas 真正的魅力在于动态绘制。来做一个简单的弹球动画:
ini
let x = 300;
let y = 200;
let dx = 3;
let dy = 2;
let radius = 20;
function animate() {
// 清空画布
ctx.clearRect(0, 0, canvas.width, canvas.height);
// 画圆
ctx.beginPath();
ctx.arc(x, y, radius, 0, Math.PI * 2);
ctx.fillStyle = '#e74c3c';
ctx.fill();
// 更新位置
x += dx;
y += dy;
// 碰壁反弹
if (x + radius > canvas.width || x - radius < 0) dx = -dx;
if (y + radius > canvas.height || y - radius < 0) dy = -dy;
requestAnimationFrame(animate);
}
animate();
核心思路很简单:
- 每一帧清空画布 →
clearRect() - 重新绘制 → 在新位置画圆
- 循环 →
requestAnimationFrame()
这就是 Canvas 动画的本质:擦掉重画,擦掉重画,擦掉重画......
九、完整示例代码
把上面所有内容整合在一起,你的 main.js 最终大概长这样:
ini
const canvas = document.getElementById('myCanvas');
const ctx = canvas.getContext('2d');
// 画对角线
ctx.beginPath();
ctx.moveTo(50, 50);
ctx.lineTo(550, 350);
ctx.strokeStyle = '#00d4ff';
ctx.lineWidth = 3;
ctx.stroke();
// 画矩形
ctx.strokeStyle = '#ff6b6b';
ctx.lineWidth = 2;
ctx.strokeRect(100, 80, 150, 100);
ctx.fillStyle = 'rgba(78, 205, 196, 0.7)';
ctx.fillRect(300, 80, 150, 100);
// 画圆
ctx.beginPath();
ctx.arc(300, 250, 60, 0, Math.PI * 2);
ctx.fillStyle = '#f7dc6f';
ctx.fill();
ctx.strokeStyle = '#f39c12';
ctx.lineWidth = 3;
ctx.stroke();
// 写文字
ctx.font = 'bold 28px "Microsoft YaHei", sans-serif';
ctx.fillStyle = '#ffffff';
ctx.textAlign = 'center';
ctx.fillText('Hello Canvas!', 300, 200);
// 弹球动画
let x = 300, y = 200, dx = 3, dy = 2, radius = 20;
function animate() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.beginPath();
ctx.arc(x, y, radius, 0, Math.PI * 2);
ctx.fillStyle = '#e74c3c';
ctx.fill();
x += dx;
y += dy;
if (x + radius > canvas.width || x - radius < 0) dx = -dx;
if (y + radius > canvas.height || y - radius < 0) dy = -dy;
requestAnimationFrame(animate);
}
animate();
十、写在最后
第一次接触 Canvas 的时候,我最大的感受是:它太"底层"了。
没有组件、没有声明式语法、没有响应式更新------你就是那个画家,每一个像素都要自己决定。画线要自己算坐标,动画要自己擦了重画,交互要自己监听鼠标位置判断命中。
但正是这种"底层",给了它无限的可能性。ECharts、AntV 这些数据可视化库底层都是 Canvas;网页游戏、图片编辑器、签名板、粒子特效......背后都有它的身影。
Canvas 的魔法不在于它有多复杂,而在于它足够简单直接------给你一块画布、一支笔,剩下的全凭你的想象力。