path 的填充与镂空 - 关于 fill-rule

参考前文:
https://hashnode.com/post/clojx8gwy000509l2gw6v53oy
前文提到过 path 实现镂空效果的办法,我遗漏了一种情况,就以这种情况作为问题来开头:
M 0 0 h 100 v 50 h -48 L 50 55 L 48 50 h -48 v -50 Z
M 10 10 h 10 v 10 h -10 v -10 z
这是一份 path 参数,画着一大一小两个矩形,但画出来的效果并不是镂空的,小矩形被大矩形遮盖,变成了“填充”的效果:

那么填充和镂空究竟是如何判断的呢?为什么两个类似的 path 显示的效果相反呢?这个问题其实关系到 fill-rule 属性。
fill-rule是一个表现属性,它定义了用来确定一个多边形内部区域的算法。
MDN 说他是一个确定内部区域的算法,他主要有两个值,nonzero 和 evenodd,这个内部就是字面意思,同理还有外部。
path 会把内部当成是自己的一部分,而外部则是自己所不包含的区域(外部不会响应事件),在填充颜色的时候,内部会被上色,而外部不会,所以前一篇文章中的镂空效果,实际上是中间的矩形区域被当成了外部。
那为什么前文中提到的的两个矩形都属于内部呢?接下来看看 fill-rule 的两个取值:
nonzero
这个值确定了某点属于该形状的“内部”还是“外部”:从该点向任意方向的无限远处绘制射线,然后检测形状与射线相交的位置。从 0 开始统计,路径上每一条从左到右(顺时针)跨过射线的线段都会让结果加 1,每条从右向左(逆时针)跨过射线的线段都会让结果减 1。当统计结束后,如果结果为 0,则点在外部;如果结果不为 0,则点在内部。
MDN 讲的很好,但我没看懂,我查阅资料后发现这个其实很好理解。
首先画两个重叠的矩形,大小矩形都按顺时针方向绘制:
M 0 0 h 50 v 50 h -50 z
M 10 10 h 20 v 20 h -20 z

大概是这样,现在要判断蓝色区域是内部还是外部,先在里面随便取一个原点,然后向任意方向发出射线:

射线与两个边均有交点,沿着路径的绘制方向和射线的方向,可以看到都是逆时针的,分数是 -2,不为 0,所以蓝色区域是内部。
那么换一种场景,大小矩形一个按逆时针绘制,另一个按顺时针绘制,还是用原来的射线法,计算出分数为 0,所以蓝色区域是外部:

因为他是外部,所以不算 path 的一部分,上色时就会镂空。
而 fill-rule 的默认取值就是 nonzero,所以在前一篇文章中,相似的 path 导致内部镂空效果不一样的原因就是这个。
由于两个图形叠加的情况太简单了,所以在这个例子中可以直接得到结论“外部和内部图形绘制方向不一样的时候,内部是镂空的,反之是填充的”。
evenodd
这种方式也是计分制,但计分的规则有些不同,还是先放 MDN 的说明:
这个值用确定了某点属于该形状的“内部”还是“外部”:从该点向任意方向无限远处绘制射线,并统计这个形状所有的路径段中,与射线相交的路径段的数量。如果有奇数个路径段与射线相交,则点在内部;如果有偶数个,则点在外部。
这个说明就很简单了,说白了就是看交点的数量,奇数就属于内部,偶数就属于外部。

如图,有两个交点,那么蓝色区域就是外部,可以看到无关路径绘制的方向。




