Canvas 路径与形状:从直线、曲线到复合路径
在 Canvas 里,真正灵活的绘制能力几乎都来自「路径」:从简单的折线到圆弧、贝塞尔曲线,再到由多个子路径构成的复杂形状。
这一篇聚焦路径相关 API,尝试把几件事讲清楚:路径是怎么被构建和复用的、常用的直线/曲线/圆弧怎么组合,以及在工程里如何把复杂图形封装成可维护的形状函数。
1. 路径的基本生命周期:构造 → 填充/描边 → 可选复用
在 Canvas 2D API 中,路径的使用大致遵循一个流程:
beginPath():开始一条新路径;- 用一系列命令(
moveTo、lineTo、arc等)构建路径; - 通过
fill()或stroke()输出; - 可以选择再次使用同一条路径(例如多次描边/填充),也可以开始下一条路径。
几个要点:
beginPath()会清空当前路径,但不会影响样式和变换等状态;closePath()会自动把当前点和起始点连接起来,形成闭合路径;- 同一条路径既可以被描边又可以被填充,顺序会影响视觉效果(通常先
fill再stroke)。
理解这一点后,后面再看复杂路径时,只需要关注「怎么构造」,输出方式可以灵活选择。
2. 直线与折线:moveTo / lineTo
构造路径最基础的两块是:
moveTo(x, y):把当前绘制点移动到某位置,不画线;lineTo(x, y):从当前点画一条直线到新位置。
一个典型的折线路径:
1ctx.beginPath();
2ctx.moveTo(x0, y0);
3ctx.lineTo(x1, y1);
4ctx.lineTo(x2, y2);
5ctx.lineTo(x3, y3);
6ctx.stroke();
工程实践中的小技巧:
- 可以把一系列点放在数组里,用循环构造路径;
- 为重复使用的折线封装函数,例如绘制折线图的线条部分。
3. 圆与圆弧:arc 与 arcTo
3.1 arc:以中心 + 半径 + 角度画圆/圆弧
arc(x, y, radius, startAngle, endAngle, anticlockwise) 的参数含义是:
(x, y):圆心坐标;radius:半径;startAngle、endAngle:起止角度(弧度制,0 代表 x 轴正方向);anticlockwise:是否逆时针绘制(默认顺时针)。
常见用法:
- 画完整圆:
1ctx.beginPath();
2ctx.arc(cx, cy, r, 0, Math.PI * 2);
3ctx.fill();
- 画扇形(配合
moveTo和closePath):
1ctx.beginPath();
2ctx.moveTo(cx, cy);
3ctx.arc(cx, cy, r, start, end);
4ctx.closePath();
5ctx.fill();
3.2 arcTo:用于圆角连接
arcTo(x1, y1, x2, y2, radius) 会根据当前点和两个目标点定义一个圆弧,常用于:
- 在两个线段之间连接一个圆角;
- 封装圆角矩形等形状。
虽然直接用 arcTo 略难直观想象,但它可以避免手算圆角切点坐标,在封装常用形状时非常有用。
4. 曲线:quadraticCurveTo 与 bezierCurveTo
4.1 二次贝塞尔:quadraticCurveTo
quadraticCurveTo(cpX, cpY, x, y) 定义了一条从当前点到 (x, y) 的二次贝塞尔曲线,(cpX, cpY) 是控制点:
- 控制点位置影响曲线的「拉伸方向」和弯曲程度;
- 适合简单平滑过渡(如对折线做平滑处理)。
4.2 三次贝塞尔:bezierCurveTo
bezierCurveTo(cp1X, cp1Y, cp2X, cp2Y, x, y) 使用两个控制点:
- 从当前点到
(x, y),中间通过(cp1X, cp1Y)和(cp2X, cp2Y)控制形状; - 适合更复杂的曲线,比如图标轮廓、手写风曲线等。
在工程实践中:
- 手写控制点坐标会比较枯燥,通常会通过设计工具导出路径数据,再在代码中使用;
- 也可以封装一些「平滑曲线」函数,用简单规则生成控制点。
5. 复合路径与「洞」:多次 beginPath 与填充规则
Canvas 支持在一条路径中包含多个子路径,常见场景包括:
- 绘制带洞的形状(例如甜甜圈、环形图块);
- 同一图形有多个不连续区域。
构造方式大致是:
- 在一次
beginPath()之后:- 使用多组
moveTo+ 绘制命令构造多个子路径; - 再统一调用
fill()或stroke()。
- 使用多组
在填充时,Canvas 使用「非零环绕规则」判断内部区域。
简单理解是:
- 子路径方向(顺时针/逆时针)会影响哪些区域被视为「内部」;
- 在需要带洞效果时,可以合理调整子路径方向或使用
clip配合。
6. 工程实践:把复杂形状封装成函数
在真实项目中,很少直接在业务逻辑里堆一大段路径命令,更好的做法是:
- 为常用形状封装函数,例如:
- 圆角矩形:
drawRoundedRect(ctx, x, y, w, h, radius); - 环形扇区:
drawDonutSlice(ctx, cx, cy, innerR, outerR, start, end); - 自定义图标:
drawCustomIcon(ctx, x, y, size);
- 圆角矩形:
- 在函数内部:
- 使用路径 API 构造形状;
- 不直接调用
fill或stroke,而是交给调用方决定如何输出。
这样可以:
- 复用图形构造逻辑;
- 在不同场景下用不同样式渲染同一种形状。
7. 小结:路径是 Canvas 绘制的「语法树」
可以把这一篇的核心点概括为:
- 路径是 Canvas 绘制的基础单位,通过
beginPath+ 一系列构造命令 +fill/stroke组合完成; - 直线、圆弧、贝塞尔曲线可以灵活组合成复杂形状;
- 多个子路径可以共享一次填充/描边,构成带洞或多部分的图形;
- 在工程实践中,通过封装常用形状函数,让路径构造逻辑集中管理,既提高可读性,也便于调整与复用。
在掌握路径之后,Canvas 就不再只是「几个矩形和圆」,而是一套可以表达任意 2D 形状的绘制语言。