CSS控制SVG填充规则:fill-rule: nonzero与evenodd的几何判定
大家好,今天我们来深入探讨SVG的填充规则,以及CSS中fill-rule属性如何控制这些规则。SVG中的填充规则决定了如何确定一个点是否位于一个形状的内部,从而决定是否应该填充该区域。理解这两种主要的填充规则——nonzero和evenodd——对于创建复杂和精确的SVG图形至关重要。
SVG与矢量图形的基础
在深入讨论填充规则之前,我们先简单回顾一下SVG和矢量图形的基本概念。
SVG(Scalable Vector Graphics)是一种基于XML的矢量图形格式。与光栅图形(如JPEG或PNG)不同,SVG图形使用数学公式来描述形状,而不是像素网格。这意味着SVG图形可以无限缩放而不会失真,非常适合用于图标、图表和用户界面元素。
矢量图形由路径(<path>元素)、基本形状(如<rect>、<circle>、<ellipse>、<line>、<polyline>、<polygon>)以及文本等元素组成。这些元素共同构成复杂的图形。
fill属性与填充区域
fill属性用于指定SVG图形的填充颜色。例如:
<svg width="200" height="100">
<rect width="100" height="50" fill="red" />
</svg>
这段代码创建了一个红色的矩形。fill属性接受颜色值,可以是颜色名称(如red、blue),十六进制颜色代码(如#FF0000),RGB值(如rgb(255, 0, 0)),等等。
但是,当图形包含重叠或嵌套的路径时,简单的fill属性不足以确定哪些区域应该被填充。这时候,fill-rule属性就发挥作用了。
fill-rule属性:定义填充算法
fill-rule属性定义了用于确定路径内部区域的算法。它有两个主要值:
nonzero(默认值)evenodd
接下来,我们将详细探讨这两种填充规则的几何判定方法,并通过代码示例进行说明。
fill-rule: nonzero:非零绕数规则
nonzero填充规则是SVG的默认填充规则。它的判定方法如下:
- 从目标点向任意方向绘制一条射线。 这条射线必须足够长,以便完全穿过要判断的形状。
- 计算路径的绕数。 对于射线与路径的每个交点,根据路径的方向进行计数:
- 如果路径从左到右穿过射线(即,路径的切线方向与射线方向大致相同),则计数 +1。
- 如果路径从右到左穿过射线(即,路径的切线方向与射线方向大致相反),则计数 -1。
- 如果绕数结果不为零,则该点位于形状内部,应该被填充。如果绕数为零,则该点位于形状外部,不应该被填充。
用表格形式来总结:
| 路径穿过射线的方向 | 绕数变化 |
|---|---|
| 从左到右 | +1 |
| 从右到左 | -1 |
示例:
考虑一个包含两个同心矩形的SVG图形,其中一个矩形是逆时针绘制的,另一个是顺时针绘制的:
<svg width="200" height="100">
<path d="M 20 20 L 20 80 L 80 80 L 80 20 Z M 40 40 L 40 60 L 60 60 L 60 40 Z" fill="red" fill-rule="nonzero" />
</svg>
在这个例子中,外部矩形是顺时针绘制的,内部矩形也是顺时针绘制的。让我们分析几个关键点:
- 外部矩形内部,内部矩形外部的点: 从该点绘制一条射线,它穿过外部矩形的一次,方向是从左到右(绕数+1)。它不穿过内部矩形。因此,绕数为1,该点位于形状内部,应该被填充。
- 内部矩形内部的点: 从该点绘制一条射线,它穿过外部矩形的一次,方向是从左到右(绕数+1),穿过内部矩形的一次,方向是从右到左(绕数-1)。因此,绕数为1 – 1 = 0,该点位于形状外部,不应该被填充。
因此,最终效果是,外部矩形被填充,而内部矩形形成一个“孔”。
更复杂的例子:
考虑一个包含多个重叠圆形的图形:
<svg width="200" height="200">
<circle cx="50" cy="50" r="40" fill="blue" fill-rule="nonzero" />
<circle cx="100" cy="50" r="40" fill="blue" fill-rule="nonzero" />
<circle cx="75" cy="100" r="40" fill="blue" fill-rule="nonzero" />
</svg>
在这个例子中,三个圆形相互重叠。由于fill-rule设置为nonzero,重叠区域的绕数会增加,但始终不为零。因此,整个区域都被填充,形成一个联合的形状。
fill-rule: evenodd:奇偶规则
evenodd填充规则的判定方法如下:
- 从目标点向任意方向绘制一条射线。
- 计算射线与路径的交点数量。 只需要统计交点的总数,不需要考虑路径的方向。
- 如果交点数量为奇数,则该点位于形状内部,应该被填充。如果交点数量为偶数,则该点位于形状外部,不应该被填充。
用表格形式来总结:
| 交点数量 | 填充状态 |
|---|---|
| 奇数 | 内部 |
| 偶数 | 外部 |
示例:
再次考虑包含两个同心矩形的SVG图形:
<svg width="200" height="100">
<path d="M 20 20 L 20 80 L 80 80 L 80 20 Z M 40 40 L 40 60 L 60 60 L 60 40 Z" fill="red" fill-rule="evenodd" />
</svg>
这次,fill-rule设置为evenodd。让我们分析几个关键点:
- 外部矩形内部,内部矩形外部的点: 从该点绘制一条射线,它穿过外部矩形的一次。交点数为1(奇数),该点位于形状内部,应该被填充。
- 内部矩形内部的点: 从该点绘制一条射线,它穿过外部矩形的一次,穿过内部矩形的一次。交点数为2(偶数),该点位于形状外部,不应该被填充。
与nonzero规则的结果相同,外部矩形被填充,而内部矩形形成一个“孔”。
一个更关键的例子:
考虑一个自相交的路径:
<svg width="200" height="100">
<path d="M 20 20 L 80 20 L 80 80 L 20 80 L 80 20 L 20 20 Z" fill="red" fill-rule="evenodd" />
</svg>
这个路径看起来像一个数字“8”。让我们分析evenodd规则如何应用:
- “8”字形的中心区域: 从该区域中的一点绘制一条射线,它会穿过路径两次。交点数为2(偶数),因此该点位于形状外部,不应该被填充。
- “8”字形的两个环: 从任一环中的一点绘制一条射线,它会穿过路径一次。交点数为1(奇数),因此该点位于形状内部,应该被填充。
因此,最终效果是,“8”字形的两个环被填充,而中心区域是空的。
现在,如果我们将fill-rule更改为nonzero:
<svg width="200" height="100">
<path d="M 20 20 L 80 20 L 80 80 L 20 80 L 80 20 L 20 20 Z" fill="red" fill-rule="nonzero" />
</svg>
在这种情况下,整个“8”字形都会被填充,因为任何从形状内部绘制的射线都会产生非零的绕数。
代码示例:动态切换fill-rule
我们可以使用JavaScript来动态切换fill-rule属性,以便更好地理解其效果。
<!DOCTYPE html>
<html>
<head>
<title>Fill Rule Demo</title>
<style>
svg {
border: 1px solid black;
}
</style>
</head>
<body>
<svg width="200" height="100" id="mySVG">
<path d="M 20 20 L 80 20 L 80 80 L 20 80 L 80 20 L 20 20 Z" fill="red" fill-rule="evenodd" id="myPath"/>
</svg>
<button onclick="toggleFillRule()">Toggle Fill Rule</button>
<script>
function toggleFillRule() {
var path = document.getElementById("myPath");
var currentFillRule = path.getAttribute("fill-rule");
if (currentFillRule === "evenodd") {
path.setAttribute("fill-rule", "nonzero");
} else {
path.setAttribute("fill-rule", "evenodd");
}
}
</script>
</body>
</html>
这段代码创建了一个包含自相交路径的SVG图形,以及一个用于切换fill-rule属性的按钮。通过点击按钮,你可以观察到evenodd和nonzero规则之间的差异。
何时使用nonzero和evenodd?
nonzero: 适用于大多数简单的填充情况,特别是当形状不自相交或嵌套时。它是默认规则,通常能提供预期的结果。evenodd: 适用于需要创建“孔”或处理自相交路径的情况。例如,它可以用于创建镂空字体或复杂的图形。
在实际开发中,选择哪种填充规则取决于所需的视觉效果。建议尝试不同的规则,并观察其对图形的影响,以便选择最合适的规则。
不同图形软件中的表现
需要注意的是,不同的图形软件(如Adobe Illustrator、Inkscape)在处理SVG时可能会有一些细微的差异。虽然fill-rule属性本身是标准化的,但软件内部的路径渲染引擎可能会以不同的方式处理某些复杂的路径。因此,在不同的软件中测试SVG图形,以确保其在目标环境中正确显示,这一点很重要。
实践中的常见问题
-
意外的孔: 如果你发现图形中出现了意外的孔,可能是因为路径的方向不正确,导致
nonzero规则计算出了错误的绕数。尝试反转路径的方向(例如,在<path>元素的d属性中使用不同的命令顺序)可能会解决问题。或者,考虑使用evenodd规则。 -
重叠区域没有被填充: 如果你希望重叠区域被填充,但它们却没有被填充,请确保
fill-rule设置为nonzero,并且路径的方向是正确的。 -
性能问题: 对于非常复杂的路径,
fill-rule的计算可能会影响性能。尽量简化路径,减少自相交和嵌套的次数,可以提高渲染速度。
深入理解路径数据
理解SVG路径数据(<path>元素的d属性)对于掌握填充规则至关重要。路径数据由一系列命令组成,每个命令都指示如何绘制路径的一部分。常见的命令包括:
M(moveto):将画笔移动到指定的坐标。L(lineto):绘制一条直线到指定的坐标。C(curveto):绘制一条贝塞尔曲线到指定的坐标。Z(closepath):闭合路径,将当前点连接到路径的起点。
通过仔细研究路径数据,你可以了解路径的方向和形状,从而更好地理解fill-rule如何影响填充效果。
例如,考虑以下路径:
<path d="M 10 10 L 90 10 L 90 90 L 10 90 Z" />
这个路径创建了一个正方形。路径按照顺时针方向绘制。如果我们将另一个逆时针绘制的正方形添加到同一个路径中,并设置fill-rule为nonzero,那么内部的正方形将会形成一个孔。
总结与要点回顾
我们详细讨论了fill-rule属性的nonzero和evenodd两种规则,分析了它们的几何判定方法,并通过代码示例进行了说明。理解这两种填充规则对于创建复杂和精确的SVG图形至关重要,能够帮助我们更好地控制图形的填充效果,避免出现意外的孔洞或未填充区域。掌握了这些知识,就能更有效地利用SVG的强大功能,创建出令人惊叹的视觉效果。
更多IT精英技术系列讲座,到智猿学院