svg之图案填充

SVG中的图案(pattern),可以理解为通过重复填充某一图形得到的图像。因此被重复填充的那个图形可视为图案的填充单元(tile,直译就是瓷砖),最终得到的图案就取决于如何在整个图案区域中重复这一填充单元以及在分配到的单元格中如何填充这个图形

图案的使用

定义

需要在defs标签内进行定义,然后必须要使用pattern标签包裹path,这样才算是定义了一个图案,也就是说pattern标签内所包裹的所有path标签组成了填充单元;比如:

1
2
3
4
5
6
<defs>
<pattern id="t1" x="0" y="0" width="20%" height="20%" patternUnits="objectBoundingBox">
<path d="M 0 0 Q 5 20 10 10 T 20 20" style="stroke: black; fill: none;"></path>
<path d="M 0 0 h 20 v 20 h -20 z" style="stroke: gray; fill: none;"></path>
</pattern>
</defs>

使用

使用的时候需要在fill属性中指定图案的路径,可以使用url()来指定;如:

1
2
3
4
5
<g id="pattern1">
<rect x="20" y="20" width="100" height="100" style="fill: url(#t1); stroke: black;"></rect>
<rect x="135" y="20" width="60" height="80" style="fill: url(#t1); stroke: black;"></rect>
<rect x="220" y="20" width="150" height="130" style="fill: url(#t1); stroke: black;"></rect>
</g>

图案的设置

patternUnits

patternUnits属性定义了图案是如何分配重复单元格的,需要配合widthheight属性来使用。

属性值 作用
objectBoundingBox
默认值
此时单元格的大小按比例计算,即被填充的元素大小为100%,单元格的大小由widthheight决定,取值为比例;比如当width='0.1'时,代表一个单元格的宽度为元素宽度的10%height也同理;
userSpaceOnUse 此时单元格的大小按用户坐标系的单位计算,并且直接铺放在用户空间(即SVG容器内),只不过铺放的起始位置需要通过xy属性进行设定;因此在该模式下的图案被使用时实际上与元素的位置相关,就像在固定的图像上截取某一块区域一样!

patternContentUnits

patternContentUnits属性则是用来定义在一个单元格内如何填充tile,即决定图案内所定义的图形(即path标签)使用的单位是什么。

属性值 作用
objectBoundingBox 此时图形绘制的单位按比例来计算,即以被填充的元素大小(而非单元格的大小!)100%,以此进行推算,因此在该模式下path内的数值需要换算成比例,不然会超出!
userSpaceOnUse
默认值
此时图形绘制按照用户坐标系的单位进行计算,并且图形的原点(0,0)位于此单元格的左上角!

举个栗子

例1

假设使用一个实际大小为20x20的填充单元:

tile

patternUnits属性设置为objectBoundingBoxpatternContentUnits属性设置为userSpaceOnUsewidth0.2height0.2时,对元素进行填充的效果为:

1
2
3
4
5
6
7
8
9
<svg width="200" height="200" id="container">
<defs>
<pattern id="t1" x="0" y="0" width="0.2" height="0.2" patternUnits="objectBoundingBox" patternContentUnits="uesrSpaceOnUse">
<path d="M 0 0 Q 5 20 10 10 T 20 20" style="stroke: black; fill: none;"></path>
<path d="M 0 0 h 20 v 20 h -20 z" style="stroke: gray; fill: none;"></path>
</pattern>
</defs>
<rect height="100" width="100" x="50" y="50" fill="url(#t1)"></rect>
</svg>

example1

分析:由于patternUnits属性设置为objectBoundingBox,所以单元格大小是按照元素的比例进行分配,widthheight都为0.2,表明单元格的大小相当于元素大小的20%,而元素大小为100x100,因此得到的单元格大小为20x20,刚好与填充图形的大小一样。而patternContentUnits属性设置为userSpaceOnUse,即表明单元格内使用的单位为用户坐标系的单位(一般默认为px)。

如果将上述中的元素大小改为130x120时,效果如下:

mark

分析:同上,分配到的单元格大小为27.5x24,而绘制的图像只有20x20,因此会出现空余的间隔,而不是想象的填满。

如果将上述中的元素大小改为80x70时,效果如下:

mark

分析:同上,分配到的单元格大小为16x14,而绘制的图像大小为20x20,因此单元格的大小不够绘制,在单元格范围外的部分就不会被显示

例2

同样使用上面20x20的填充图形;当patternUnits属性设置为userSpaceOnUsepatternContentUnits属性设置为userSpaceOnUsewidth20height20时,x=0y=0,对元素进行填充的效果为:

mark

分析:由于patternUnits属性设置为userSpaceOnUse,所以单元格的分配实际上就放到SVG画布中去了,与元素本身无关了;widthheight都为20,所以单元格的大小为20x20,然后设置的xy都为0,也就是说图案填充的起点为(0,0);因此实际上就相当于在画布中(0,0)的开始填充大小为20x20的图形,然后元素所在位置(50,50)截取一块大小为元素本身大小100x100的图案作为填充图案!

从画布(0,0)开始填充:

mark

然后截取从(50,50)开始截取一块大小为100x100的区域:

mark

如果将widthheight改为10x10,其他的都不变,效果如下:

mark

分析:同上,由于单元格的大小变成了25x25,所以在单元格内绘制填充单元后还有剩余空间,所以看起来就有间隔了。

如果保持单元格大小为25x25,而xy的坐标变为(15,15),效果如下:

mark

分析:同上,实际上就是图案在画布上的填充起点发生了改变了,变成了下面这样:

mark

可以看到,填充的方向实际上是由起点向四周进行,而不是只向右和下进行!

例3

patternUnits属性设置为 objectBoundingBoxpatternContentUnits属性设置为 objectBoundingBoxwidth0.2height0.2时,如果还是使用上面20x20的填充图形;那么对元素进行填充的效果就会变成:

mark

1
2
3
4
5
6
7
8
9
<svg width="200" height="200" id="container">
<defs>
<pattern id="t1" width="0.2" height="0.2" patternUnits="objectBoundingBox" patternContentUnits="objectBoundingBox">
<path d="M 0 0 Q 5 20 10 10 T 20 20" style="stroke: black; fill: none;"></path>
<path d="M 0 0 h 20 v 20 h -20 z" style="stroke: gray; fill: none;"></path>
</pattern>
</defs>
<rect width="100" height="100" x="50" y="50" fill="url(#t1)"></rect>
</svg>

分析patternUnits objectBoundingBox,且widthheight都为0.2,元素本身大小为100x100,因此单元格大小为20x20;但是问题是patternContentUnits objectBoundingBox,因此图案内部绘制时的单位为比例,而填充图形内stroke-width1,也就是说stroke-width的大小实际上为整个元素的大小,因此也就超出了整个单元格!所以需要对填充图形内的path中的单位进行修正,全部转化为比例

1
2
3
4
<pattern id="t1" width="0.2" height="0.2" patternUnits="objectBoundingBox" patternContentUnits="objectBoundingBox">
<path d="M 0 0 Q 0.05 0.20 0.1 0.1 T 0.20 0.20" style="stroke: black; fill: none;stroke-width: 0.01;"></path>
<path d="M 0 0 h 0.20 v 0.20 h -0.20 z" style="stroke: gray; fill: none;stroke-width: 0.01;"></path>
</pattern>

修正填充图形后的效果为:

mark

如果将元素大小改为130x120后,效果如下:

mark

分析:同理,单元格大小为27.5x24,但是由于图案内部的填充图形只有0.2的比例大小,而正好与单元格的大小比例一致,因此填充之间没有出现间隙;可以把widthheight的大小设置为0.25,此时单元格比例和图案内部的图形的比例不一致,可以看看此时的效果:

mark

可以看到填充的图案并没有按比例进行,因为图案内部的填充图形本身只用了0.2的比例大小,而这个比例是对于被填充元素的本身大小而言,所以此时填充图形并没有单元格大小那么大!所以就相当于单元格内看起来有空余部分。(P.S:这里真的很有疑问,为啥不把比例相对于单元格进行设计,而是非得相对于被填充元素本身?难道不是对于单元格进行比例设计会更方便一些吗?!!

例4

patternUnits属性设置为 userSpaceOnUsepatternContentUnits属性设置为 objectBoundingBoxwidth20height20时,xy都为0,使用按比例进行修正的填充图形,效果如下:

mark

1
2
3
4
5
6
7
8
9
<svg width="200" height="200" id="container">
<defs>
<pattern id="t1" x="0" y="0" width="20" height="20" patternUnits="userSpaceOnUse" patternContentUnits="objectBoundingBox">
<path d="M 0 0 Q 0.05 0.20 0.1 0.1 T 0.20 0.20" style="stroke: black; fill: none;stroke-width: 0.01;"></path>
<path d="M 0 0 h 0.20 v 0.20 h -0.20 z" style="stroke: gray; fill: none;stroke-width: 0.01;"></path>
</pattern>
</defs>
<rect width="100" height="100" x="50" y="50" fill="url(#t1)"></rect>
</svg>

分析:需要注意的是,这里的单元格仍然是在画布上进行排列,但是对于填充图形的绘制则是相当于被填充元素本身的大小按照比例进行的,而图案内部的填充图形比例为0.2,所以只是刚好与单元格的大小相匹配而已!如果把单元格大小设置为25x25,效果如下:

mark

可以看到单元格分布确实是截取画布上进行排列过的,但是单元格内的填充元素确实是按照被填充元素大小进行比例绘制的,可以通过两个完全不同大小的元素填充上述的图案进行对比:

mark

如图,填充元素大小为200x200100x100的单元格分布时是一致的,但是可以明显看到填充的图案不同;填充元素大小为200x200时,由于图案内部的填充图形只占了0.2的比例,所以此时绘制出来后大小则为40x40;同理当填充元素大小为100x100时,绘制出来后的大小为20x20,因此图案完全不同!

例子总结

  • 当使用patternContentUnits属性的objectBoundingBox时,尤其需要注意图案绘制时的比例大小是相对于被填充元素本身的!

图案的嵌套

一个被定义的图案,可以在其他图案的填充图形中被使用(即在内部的元素中使用fill属性即可),这就是图案的嵌套。

参考文档

  1. 《SVG精髓(第2版)》