Canvas中clip()结合arc()创建裁剪区域时路径意外闭合问题
问题
为何在沿二次曲线路径采样得到的一系列点上调用CanvasRenderingContext2D.arc()绘制图形后,使用CanvasRenderingContext2D.clip()创建裁剪区域时会出现额外的路径闭合现象?已知clip()搭配arc()会导致路径意外闭合,而搭配rect()则表现符合预期。
背景
正在尝试创建沿纵向拆分颜色的二次曲线段路径,计划通过以下步骤实现需求:
- 绘制二次曲线路径
- 在二次曲线上进行点采样
- 创建裁剪区域,在每个采样点处绘制圆形
对应实现代码:
let region = new Path2D(); for (j = 0; j < pointsQBez.length; j++) { region.arc(pointsQBez[j].x, pointsQBez[j].y, 4, 0, 2 * Math.PI ); } ctx.clip(region)
- 基于曲线将画布拆分为两个区域
- 计算曲线起止段与画布边界的交点
- 闭合路径(作为第一裁剪区域)
- 在整个画布上绘制矩形(作为第二裁剪区域)
- 为第四步生成的两个区域填充颜色
步骤3、4、5的效果如下图所示:


问题现象
第三张图中的粉色区域厚度本应与青绿色区域一致,但实际曲线的整个内部区域都被意外填充。
额外观察结果
- 若使用
CanvasRenderingContext2D.rect()替代CanvasRenderingContext2D.arc(),则不会出现该异常现象,效果如下:


- 使用
CanvasRenderingContext2D.arc()时,被意外填充的曲线内部区域表现并不一致,效果如下:


原因说明
该现象是Canvas 2D路径绘制规范的明确行为,和clip()方法本身没有关系:
arc()方法(包括Path2D实例的同名方法)的执行逻辑规定:如果当前路径已经存在绘制完成的子路径,会先添加一条从当前路径终点到本次圆弧起点的直线段,再开始绘制圆弧。- 循环调用
arc()绘制整圆时,上一个圆绘制完成后,路径终点停留在上一个圆的0角度位置(即圆心右侧、坐标为(上一个点x + 半径, 上一个点y)的点);下一个圆的起点是当前采样点的0角度位置,两个点不重合,就会自动生成一条连接两点的直线段。 - 这些自动生成的直线段和圆形轮廓共同围出了二次曲线内部的大面积闭合区域,
clip()按照默认的非零环绕填充规则计算裁剪范围时,会把这部分意外围合的区域也包含在内,最终出现曲线内部被意外填充的问题。 - 观察到的
arc()填充效果不一致,是因为不同二次曲线的采样点位置不同,自动生成的连线走向不同,围出的闭合区域形状自然存在差异。 rect()不会触发该问题,是因为规范要求rect()绘制时会隐式创建独立的新子路径,直接从矩形左上角起点开始绘制,不会自动连接上一个路径的终点,不会产生额外连线。
修复方法
每次调用arc()绘制圆形前,手动调用moveTo()将画笔移动到本次圆弧的起始点,打断自动连线逻辑即可。修复后的代码如下:
let region = new Path2D(); const radius = 4; for (j = 0; j < pointsQBez.length; j++) { const x = pointsQBez[j].x; const y = pointsQBez[j].y; // 先移动到本次圆弧的0角度起点,避免自动生成连线 region.moveTo(x + radius, y); region.arc(x, y, radius, 0, 2 * Math.PI); } ctx.clip(region);
如果需要规避其他潜在的路径连接问题,也可以在每次arc()调用后追加region.closePath(),对于绘制整圆的场景,提前moveTo()已经足够解决问题。
内容的提问来源于stack exchange,提问作者L.Lauenburg
相关产品推荐
相关产品推荐

