绘制基本图形:从零开始构建你的画布世界
各位同学,欢迎来到第二章。上一章我们搭好了画布环境,今天就来真正动手画点东西。说实话,Canvas 最迷人的地方就在于——你只需要几行代码,就能在浏览器里画出任何形状。矩形、圆形、直线、路径、贝塞尔曲线,这些就是你的画笔和颜料。
我个人习惯把基本图形分为两类:简单几何和复杂路径。前者有现成的 API,后者需要你手动描述轨迹。咱们一个一个来。
2.1 绘制矩形:最直接的图形
矩形是 Canvas 里最简单的图形。为什么?因为它只有四个点,而且 API 直接给了三个方法:fillRect、strokeRect、clearRect。
核心方法速览
fillRect(x, y, width, height)— 填充矩形strokeRect(x, y, width, height)— 描边矩形clearRect(x, y, width, height)— 清除矩形区域
看个例子就明白了:
const canvas = document.getElementById('myCanvas');
const ctx = canvas.getContext('2d');
// 画一个红色填充矩形
ctx.fillStyle = '#FF6B6B';
ctx.fillRect(50, 50, 200, 100);
// 画一个蓝色描边矩形
ctx.strokeStyle = '#4ECDC4';
ctx.lineWidth = 4;
ctx.strokeRect(300, 50, 200, 100);
// 清除中间一块区域
ctx.clearRect(100, 70, 100, 60);
嗯,这里要注意:clearRect 不是擦除像素,而是把指定区域变成完全透明。我在项目中遇到过一个问题——想用 clearRect 实现橡皮擦效果,结果发现它把背景也擦没了。后来才意识到,应该用 globalCompositeOperation 的 'destination-out' 模式。
小技巧:如果你想让矩形有圆角,别用 rect 方法。直接用 ctx.roundRect(x, y, w, h, radius)(现代浏览器支持),或者自己画路径。
2.2 绘制圆形:用 arc 画弧
圆形在 Canvas 里其实是用「弧」来画的。API 是 arc(x, y, radius, startAngle, endAngle, counterclockwise)。注意角度用的是弧度制,不是角度制。
为什么会这样?因为数学上弧度更自然。一个完整的圆是 2π 弧度,半圆是 π。你想想看,如果你要画一个 90 度的扇形,就得写 Math.PI / 2。
// 画一个完整的圆
ctx.beginPath();
ctx.arc(200, 200, 100, 0, Math.PI * 2);
ctx.fillStyle = '#FFE66D';
ctx.fill();
// 画一个半圆弧
ctx.beginPath();
ctx.arc(400, 200, 100, 0, Math.PI);
ctx.strokeStyle = '#95E1D3';
ctx.lineWidth = 6;
ctx.stroke();
我曾经在做一个仪表盘项目时,需要画一个 270 度的弧形进度条。一开始直接用 arc 从 0 到 1.5π,结果发现方向反了。后来才搞清楚——counterclockwise 参数控制绘制方向,默认是顺时针(false)。
避坑指南:画弧之前一定要调用 beginPath()!否则上次的路径会残留,画出奇怪的连线。我曾经因为这个 bug 调试了半小时,最后发现是忘了 beginPath。
2.3 绘制直线:最简单的路径
直线是路径的基础。用 moveTo 移动画笔,用 lineTo 画线到目标点。说白了就是「抬笔、落笔、画线」的过程。
ctx.beginPath();
ctx.moveTo(50, 50); // 起点
ctx.lineTo(250, 50); // 画到右边
ctx.lineTo(250, 200); // 画到下边
ctx.lineTo(50, 200); // 画回左边
ctx.closePath(); // 闭合路径(回到起点)
ctx.stroke();
你想想看,上面这段代码其实画了一个矩形边框。但和 strokeRect 不同,用路径你可以控制每个顶点的样式,比如画虚线、加圆角。
我建议你养成一个习惯:画路径时总是先 beginPath,画完后调用 stroke 或 fill。这样代码结构清晰,也不容易出 bug。
2.4 绘制路径:自由创作的核心
路径是 Canvas 最强大的功能。你可以用 moveTo、lineTo、arc、bezierCurveTo 等方法组合出任意形状。
路径的基本流程是固定的:
beginPath()— 开始新路径moveTo()— 移动到起点- 各种画线/画弧方法
closePath()— 可选,闭合路径fill()或stroke()— 渲染
来看一个复杂点的例子——画一个五角星:
function drawStar(ctx, cx, cy, spikes, outerRadius, innerRadius) {
let rot = Math.PI / 2 * 3;
let x = cx;
let y = cy;
let step = Math.PI / spikes;
ctx.beginPath();
ctx.moveTo(cx, cy - outerRadius);
for (let i = 0; i < spikes; i++) {
x = cx + Math.cos(rot) * outerRadius;
y = cy + Math.sin(rot) * outerRadius;
ctx.lineTo(x, y);
rot += step;
x = cx + Math.cos(rot) * innerRadius;
y = cy + Math.sin(rot) * innerRadius;
ctx.lineTo(x, y);
rot += step;
}
ctx.lineTo(cx, cy - outerRadius);
ctx.closePath();
ctx.fillStyle = '#FFD93D';
ctx.fill();
ctx.strokeStyle = '#6BCB77';
ctx.lineWidth = 3;
ctx.stroke();
}
drawStar(ctx, 300, 200, 5, 100, 40);
这段代码我用了很多次。说白了就是通过三角函数计算每个顶点的坐标,然后连线。你只要理解了角度和半径的关系,任何多边形都能画出来。
个人经验:画复杂路径时,先用 ctx.save() 和 ctx.restore() 包裹代码。这样你可以临时修改样式,画完自动恢复,不影响后续绘制。
2.5 绘制贝塞尔曲线:让线条优雅起来
贝塞尔曲线是计算机图形学的核心。Canvas 提供了两种:二次贝塞尔和三次贝塞尔。
| 类型 | 方法 | 控制点数量 | 用途 |
|---|---|---|---|
| 二次贝塞尔 | quadraticCurveTo(cpx, cpy, x, y) |
1 个 | 简单弧线、抛物线 |
| 三次贝塞尔 | bezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y) |
2 个 | S 形曲线、复杂形状 |
二次贝塞尔只有一个控制点,曲线会向这个点弯曲。三次贝塞尔有两个控制点,你可以做出更复杂的形状,比如 S 形。
// 二次贝塞尔曲线
ctx.beginPath();
ctx.moveTo(50, 300);
ctx.quadraticCurveTo(200, 100, 350, 300);
ctx.strokeStyle = '#FF6B6B';
ctx.lineWidth = 4;
ctx.stroke();
// 三次贝塞尔曲线(S 形)
ctx.beginPath();
ctx.moveTo(400, 300);
ctx.bezierCurveTo(450, 100, 550, 500, 600, 300);
ctx.strokeStyle = '#4ECDC4';
ctx.lineWidth = 4;
ctx.stroke();
我记得第一次用贝塞尔曲线时,完全搞不懂控制点怎么放。后来发现一个规律:控制点就是曲线「想要去」的方向。你想让曲线往右上弯,控制点就放在右上方向。
避坑指南:贝塞尔曲线的起点是当前画笔位置(上次 moveTo 或 lineTo 的位置)。如果你忘了 moveTo,曲线会从 (0,0) 开始画,结果完全不是你想要的。
2.6 综合示例:画一个笑脸
把今天学的知识串起来,画一个简单的笑脸:
// 脸(圆形)
ctx.beginPath();
ctx.arc(300, 200, 150, 0, Math.PI * 2);
ctx.fillStyle = '#FFE66D';
ctx.fill();
ctx.strokeStyle = '#333';
ctx.lineWidth = 4;
ctx.stroke();
// 左眼(矩形)
ctx.fillStyle = '#333';
ctx.fillRect(230, 150, 30, 40);
// 右眼(矩形)
ctx.fillRect(340, 150, 30, 40);
// 嘴巴(二次贝塞尔曲线)
ctx.beginPath();
ctx.moveTo(220, 240);
ctx.quadraticCurveTo(300, 320, 380, 240);
ctx.strokeStyle = '#333';
ctx.lineWidth = 6;
ctx.stroke();
你看,只用了几行代码,一个笑脸就出来了。这就是 Canvas 的魅力——简单、直接、高效。
小结
今天咱们学了五种基本图形的绘制方法。我个人觉得,最重要的是理解路径的概念。矩形和圆形只是路径的特例,而贝塞尔曲线是路径的进阶。你只要掌握了 beginPath、moveTo、lineTo、closePath 这套流程,任何图形都不在话下。
下一章我们会讲颜色和样式,到时候你就可以给这些图形上色、加渐变、做阴影了。嗯,那才是真正让画布「活」起来的关键。
公众号:蓝海资料掘金营,微信deep3321