Canvas fill()默认采用非零环绕规则:从点向右引射线,路径下→上交点+1、上→下交点−1,环绕数非零则填充;多子路径绕向叠加计算,支持"evenodd"替代方案。

Canvas 中的 fill() 默认使用非零环绕(Non-zero Winding)规则判断路径内部区域,这是决定哪些部分被填充的核心逻辑,不是简单看“是否闭合”或“是否在形状内”。
什么是非零环绕原则
该规则通过计算从某点向任意方向(通常向右)引一条射线,与路径各段交点的“环绕数”总和来判定:若总和不为零,则该点属于填充区域。
关键在于每条交点都带符号——路径从下往上穿过射线时 +1,从上往下穿过时 −1。例如:
- 一个逆时针绘制的矩形:绕行方向使内部点的环绕数为 +1 → 填充
- 一个顺时针绘制的圆形套在逆时针矩形内:内部小圆区域环绕数 = +1(外矩形)+ (−1)(内圆)= 0 → 不填充(形成“镂空”效果)
Canvas 中如何影响环绕数
环绕数由路径绘制方向(顺时针/逆时针)和路径叠加方式共同决定,fill() 自动累加所有子路径的贡献:
立即学习“前端免费学习笔记(深入)”;
- 用
beginPath()+ 多次moveTo()/lineTo()可定义多个独立子路径 - 每个子路径的顶点顺序决定其对环绕数的符号贡献
- 无需手动计算,但需注意:同一
beginPath()内的多个闭合形状会按实际绕向叠加环绕数
对比奇偶规则(evenodd)
Canvas 也支持奇偶规则,只需在 fill() 中传入 "evenodd":
ctx.fill("evenodd"); // 仅统计交点个数,奇数则填充,与方向无关
它更直观(适合布尔运算式图形),但非默认;非零环绕更能自然表达“内外嵌套”语义,比如字体轮廓、地图多边形洞等场景。
调试技巧:验证你画的是什么
当填充结果不符合预期,大概率是子路径绕向或叠加关系出错:
- 用
stroke()先描边,观察各段走向(配合lineCap和箭头示意) - 把复杂路径拆成单个闭合形状,逐个
fill()测试 - 对嵌套形状,统一用
ctx.closePath()并明确起始点位置,避免隐式连线干扰绕向


















