大家都知道svg图像之所以能被随意拉伸且保持不失真,主要原因是它能进行矢量缩放,而这种特性主要取决于两个属性,即viewBox和preserveAspectRatio。这 篇文章就svg的这两个属性进行简要讲解。
1、viewBox
viewBox定义了svg中的内容可画的区域(相当于放在视口底下的一块画布),它的语法是:
viewBox = "<min-x> <min-y> <width> <height>"
其中min-x和min-y代表绘画
区域的左上角坐标,width和height代表绘画区域的宽高。
视口指的是svg的实际可见区域,它的大小是由svg元素的width和height两个属性来决定的。注意width、height属性与viewBox值中对应成分的区别,viewBox的值中的成分只表示svg中的内容的理论可画区域的大小,即一块假想画布的大小;这块画布在作完画后会放到svg的实际可见区域(即视口)中,并且会根据另一个属性preserveApsectRatio进行缩放以及放到视口的正确位置,这在之后的内容中会提到。当然,有时候svg元素上不会设置width和height属性,此时svg的实际尺寸应当由包裹它的外部元素来确定;也可能不设置viewBox属性,此时该属 性的值中min-x
、min-y都为0,width、height等于svg元素的实际width、height。
viewBox是一个通用属性,可以在以下元素中使用:
<marker>
<pattern>
<svg>
<symbol>
<view>
接下来的例子为了方便就以<svg>元素为例(这些案例的preserveAspectRatio都为默认值):
①先来看显示设置svg宽高的情况:
<div style="
width: 240px;
height: 100px;
background-color: rgb(127,255,212);
">
<svg viewBox="0 0 50 50"
width="80" height="80"
xmlns="http://www.w3.org/2000/svg"
style="float: left;border: 1px dashed gray;margin-right: 10px;"
>
<circle cx="50%" cy="50%" r="10" fill="red"></circle>
</svg>
<svg viewBox="-10 -10 60 80"
width="120" height="160"
xmlns="http://www.w3.org/2000/svg"
style="float: left;border: 1px dashed gray;"
>
<circle cx="20" cy="30" r="20" fill="gold"></circle>
</svg>
</div>
第一个svg的宽高被设置为80、80,其viewBox被设置为"0 0 50 50",它们的宽高比都为1 : 1,这意味着viewBox定义的区域经缩放会完全拟合(区域的四个角与视口重合)到视口中,不受preserveAspectRatio的影响。容易得出viewBox的缩放率为1.6,并且与视口左上角重合的viewBox左上角的坐标为(0, 0)。在svg中画了一个圆,圆心坐标使用的是百分比值,它表示viewBox中定义的宽或高的百分比,当然,在视口中实际呈现时还要乘上缩放率。这个例子上的圆心坐标在viewBox中即为cx=50*50%=25、cy=50*50%=25。
第二个svg的宽高被设置为120、160,其viewBox被设置为"-10 -10 60 80",它们的宽高比同样都为1 : 1,意味着viewBox的缩放不受preserveAspectRatio的影响。此时,viewBox定义区域的左上角为坐标系的(-10, -10)点,x轴和y轴的长度分别为60、80,viewBox在视口中的缩放率为2。viewBox中的圆其圆心坐标为(20, 30),注意在这里设置坐标为(30, 40)则无法居中,因为左上角的坐标为(-10, -10)而不是(0, 0);再根据坐标系宽高,其右下角的坐标应该为(50, 70)而不是(60, 80)。这个svg的实际视口高度超过了外部div元素的高度但还是完整地显示了出来,要把超出部分隐藏掉可以在div元素上设置css属性overflow: hidden。
②第二种情况,不设置宽高:
<div style="
width: 80px;
height: 80px;
border: 1px dashed gray;
">
<svg viewBox="-10 -10 40 60"
xmlns="http://www.w3.org/2000/svg"
style="background-color: rgba(128, 254, 195, 0.4);"
>
<rect x="0" y="5" width="20" height="35" fill="burlywood"></rect>
</svg>
</div>
这幅图中的svg未设置width和height,只设置了viewBox为"-10 -10 40 60",在其外部有一个宽高分别为80、80的div元素(用虚线框包裹)。可以看到,在未显式设置宽高时,svg视口的宽度取决于外部元素的宽度,宽高比与viewBox的一致(在这里是40 : 60 = 2 : 3)。viewBox经过缩放,它的宽紧随视口的变化,被缩放到与外部div的宽同样大小(在这里是放大80/40=2倍),且宽高比保持不变。
2、
preserveAspectRatio
该属性表示当svg元素中viewBox定义区域的宽高比与视口的宽高比不同时,viewBox定义区域拟合到视口中的方式。该属性的语法为:
preserveAspectRatio = "<align> [<meetOrSlice>] "
它包含两个成分,align表示当viewBox的宽高比不匹配视口的实际宽高比时的对齐方式,它有10个可能值:
- none:对viewBox进行不规则缩放,使其边界矩形完全充满整个视口。设置该值时对<meetOrSlice>的设置无效。
- xMinYMin:对viewBox进行规则缩放,把viewBox的<min-x>与视口的最小x值对齐,把viewBox的<min-y>与视口的最小y值对齐。这相当于把viewBox的左上角与视口的左上角对齐。
- xMidYMin:对viewBox进行规则缩放,把viewBox的中点x值与视口的中点x值对齐,把viewBox的<min-y>与视口的最小y值对齐。这相当于viewBox在视口的顶部居中。
- xMaxYMin:对viewBox进行规则缩放,把viewBox的<min-x>+<width>值与视口的最大x值对齐,把viewBox的<min-y>与视口的最小y值对齐。这相当于把viewBox的右上角与视口的右上角对齐。
- xMinYMid:对viewBox进行规则缩放,把viewBox的<min-x>与视口的最小x值对齐,把viewBox的中点y值与视口的中点y值对齐。这相当于viewBox在视口的最左侧居中。
- xMidYMid(默认):对viewBox进行规则缩放,把viewBox的中点x值与视口的中点x值对齐,把viewBox的中点y值与视口的中点y值对齐。这相当于viewBox的中点与视口的中点重合。
- xMaxYMid:对viewBox进行规则缩放,把viewBox的<min-x>+<width>值与视口的最大x值对齐,把viewBox的中点y值与视口的中点y值对齐。这相当于viewBox在视口的最右侧居中。
- xMinYMax:对viewBox进行规则缩放,把viewBox的<min-x>与视口的最小x值对齐,把viewBox的<min-y>+<height>值与视口的最大y值对齐。这相当于把viewBox的左下角与视口的左下角对齐。
- xMidYMax:对viewBox进行规则缩放,把viewBox的中点x值与视口的中点x值对齐,把viewBox的<min-y>+<height>值与视口的最大y值对齐。这相当于viewBox在视口的底部居中。
- xMaxYMax:对viewBox进行规则缩放,把viewBox的<min-x>+<width>值与视口的最大x值对齐,把viewBox的<min-y>+<height>值与视口的最大y值对齐。这相当于把viewBox的右下角与视口的右下角对齐。
meetOrSlice表示当viewBox的宽高比不匹配视口的实际宽高比时对viewBox的缩放方式,它是可选的,值可以是:
- meet(默认):按照如下方式缩放图像:⑴ 图像宽高比保持不变。⑵ 整个viewBox包含于视口。⑶ viewBox尽可能放大,同时满足<align>参数的条件。该方式的缩放率等于min{ 视口的width/viewBox的width,视口的height/viewBox的height }。
- slice:按照如下方式缩放图像:⑴ 图像宽高比保持不变。⑵ 视口包含于整个viewBox。⑶ viewBox尽可能缩小,同时满足<align>参数的条件。该方式的缩放率等于max{ 视口的width/viewBox的width,视口的height/viewBox的height }。
该属性是一个通用属性,可以在以下元素中使用:
- <svg>
- <symbol>
- <image>
- <feImage>
- <marker>
- <pattern>
- <view>
接下来的例子为了方便就以<svg>元素为例:
① preserveAspectRatio = "none"
<div style="
width: 400px;
height: 200px;
margin: 40px;
border: 1px dashed gray;
box-sizing: content-box;
">
<svg viewBox="0 0 400 200"
xmlns="http://www.w3.org/2000/svg"
>
<defs>
<g id="face">
<title>一个在viewBox="0 0 100 100"的假想画布上所画的图</title>
<circle cx="50" cy="50" r="40" fill="yellow" />
<circle cx="35" cy="35" r="5" fill="red" />
<circle cx="65" cy="35" r="5" fill="red" />
<path d="M 30,60 A 30,30 0 0 0 70,60"
style="stroke: #333333;stroke-width: 4;stroke-linecap: round;fill: none;"
/>
</g>
</defs>
<rect x="0" y="0" width="180"
height="150" fill="rgb(128, 254, 195)" />
<text x="170" y="70" transform="rotate(90 180 73)">150</text>
<text x="80" y="170">180</text>
<!--画图-->
<svg viewBox="0 0 100 100"
width="180" height="150" preserveAspectRatio="none" >
<use href="#face" />
</svg>
<rect x="210" y="0" width="150"
height="150" fill="rgb(128, 254, 195)" />
<text x="350" y="70" transform="rotate(90 360 73)">150</text>
<text x="275" y="170">150</text>
<!--画图-->
<svg viewBox="0 0 100 100"
width="150" height="150" x="210" y="0">
<use href="#face" />
</svg>
</svg>
</div>
在这幅图中,原脸是按照viewBox宽高比为100 : 100的设计图绘制的,其中第一个板块的视口宽高比为180:150。可以看到,第一个板块中设置preserveAspectRatio = "none"时会把viewBox定义的区域不规则缩放直至充满整个视口,其中宽度放大为1.8倍,高度放大为1.5倍,这导致了图像的失真。第二个板块是宽高比相等时所画的图,它的形状不受preserveAspectRatio的影响。
② preserveAspectRatio = "xMinYMin meet"
<div style="
width: 400px;
height: 520px;
margin: 40px;
border: 1px dashed gray;
box-sizing: content-box;
">
<svg viewBox="0 0 400 520"
xmlns="http://www.w3.org/2000/svg"
>
<defs>
<g id="face">
<title>一个在viewBox="0 0 100 100"的假想画布上所画的图</title>
<circle cx="50" cy="50" r="40" fill="yellow" />
<circle cx="35" cy="35" r="5" fill="red" />
<circle cx="65" cy="35" r="5" fill="red" />
<path d="M 30,60 A 30,30 0 0 0 70,60"
style="stroke: #333333;stroke-width: 4;stroke-linecap: round;fill: none;"
/>
</g>
</defs>
<g transform="translate(10 10)">
<rect x="0" y="0" width="160"
height="120" fill="rgb(128, 254, 195)" />
<text x="150" y="50" transform="rotate(90 160 53)">120</text>
<text x="70" y="135">160</text>
<text x="10" y="155">width>height且</text>
<text x="10" y="175">viewBox被放大</text>
<!--画图-->
<svg viewBox="0 0 100 100"
width="160" height="120"
preserveAspectRatio="xMinYMin meet"
>
<rect x="0" y="0" width="100%" height="100%" fill="blue">
<title>viewBox区域(实际为120*120)</title>
</rect>
<use href="#face" />
</svg>
</g>
<g transform="translate(220 10)">
<rect x="0" y="0" width="140"
height="180" fill="rgb(128, 254, 195)" />
<text x="130" y="80" transform="rotate(90 140 83)">180</text>
<text x="60" y="195">140</text>
<text x="10" y="215">width<height且</text>
<text x="10" y="235">viewBox被放大</text>
<!--画图-->
<svg viewBox="0 0 100 100"
width="140" height="180"
preserveAspectRatio="xMinYMin meet"
>
<rect x="0" y="0" width="100%" height="100%" fill="blue">
<title>viewBox区域(实际为140*140)</title>
</rect>
<use href="#face" />
</svg>
</g>
<g transform="translate(10 300)">
<rect x="0" y="0" width="80"
height="60" fill="rgb(128, 254, 195)" />
<text x="70" y="25" transform="rotate(90 80 28)">60</text>
<text x="30" y="75">80</text>
<text x="0" y="95">width>height且</text>
<text x="0" y="115">viewBox被缩小</text>
<!--画图-->
<svg viewBox="0 0 100 100"
width="80" height="60"
preserveAspectRatio="xMinYMin meet"
>
<rect x="0" y="0" width="100%" height="100%" fill="blue">
<title>viewBox区域(实际为60*60)</title>
</rect>
<use href="#face" />
</svg>
</g>
<g transform="translate(220 300)">
<rect x="0" y="0" width="60"
height="90" fill="rgb(128, 254, 195)" />
<text x="50" y="40" transform="rotate(90 60 43)">90</text>
<text x="20" y="105">60</text>
<text x="0" y="125">width<height且</text>
<text x="0" y="145">viewBox被缩小</text>
<!--画图-->
<svg viewBox="0 0 100 100"
width="60" height="90"
preserveAspectRatio="xMinYMin meet"
>
<rect x="0" y="0" width="100%" height="100%" fill="blue">
<title>viewBox区域(实际为60*60)</title>
</rect>
<use href="#face" />
</svg>
</g>
<text x="60" y="490">preserveAspectRatio = "xMinYMin meet"</text>
</svg>
</div>
在这幅图中,原脸是按照viewBox宽高比为100 : 100的设计图绘制,前两个板块中的脸分别被放大1.2倍和1.4倍,后两个板块中的脸分别缩小为0.6倍和0.6倍;蓝色区域是viewBox经缩放后得到的区域,多出的海蓝色部分属于视口。可以看到,在缩放方式设置为meet的时候,viewBox经过缩放会包含在视口中,它的缩放率等于min{ 视口的width/viewBox的width,视口的height/viewBox的height };当对齐方式设置为xMinYMin时,viewBox的<min-x>、<min-y>分别与视口的最小x值、最小y值对齐,这相当于viewBox的左上角与视口的左上角对齐。
③ preserveAspectRatio = "xMidYMax slice"
<div style="
width: 440px;
height: 540px;
margin: 40px;
border: 1px dashed gray;
box-sizing: content-box;
">
<svg viewBox="0 0 440 540"
xmlns="http://www.w3.org/2000/svg"
>
<defs>
<g id="face">
<title>一个在viewBox="0 0 100 100"的假想画布上所画的图</title>
<circle cx="50" cy="50" r="40" fill="yellow" />
<circle cx="35" cy="35" r="5" fill="red" />
<circle cx="65" cy="35" r="5" fill="red" />
<path d="M 30,60 A 30,30 0 0 0 70,60"
style="stroke: #333333;stroke-width: 4;stroke-linecap: round;fill: none;"
/>
</g>
</defs>
<rect x="10" y="10" width="160"
height="160" fill="rgb(128, 254, 195)" />
<circle cx="90" cy="90" r="64"
style="fill: none;stroke: #646464;stroke-dasharray: 4 2;" />
<rect x="220" y="10" width="180"
height="180" fill="rgb(128, 254, 195)" />
<circle cx="310" cy="100" r="72"
style="fill: none;stroke: #646464;stroke-dasharray: 4 2;" />
<rect x="10" y="300" width="80"
height="80" fill="rgb(128, 254, 195)" />
<circle cx="50" cy="340" r="32"
style="fill: none;stroke: #646464;stroke-dasharray: 4 2;" />
<rect x="220" y="300" width="90"
height="90" fill="rgb(128, 254, 195)" />
<circle cx="265" cy="345" r="36"
style="fill: none;stroke: #646464;stroke-dasharray: 4 2;" />
<!------------------>
<g transform="translate(10 50)">
<text x="150" y="50" transform="rotate(90 160 53)">120</text>
<text x="70" y="135">160</text>
<text x="10" y="155">width>height且</text>
<text x="10" y="175">viewBox被放大</text>
<!--画图-->
<svg viewBox="0 0 100 100"
width="160" height="120"
preserveAspectRatio="xMidYMax slice"
>
<rect x="0" y="0" width="100%" height="100%" fill="blue">
<title>viewBox区域(实际为160*160)</title>
</rect>
<use href="#face" />
</svg>
</g>
<g transform="translate(240 10)">
<text x="150" y="80" transform="rotate(90 160 83)">180</text>
<text x="60" y="195">140</text>
<text x="10" y="215">width<height且</text>
<text x="10" y="235">viewBox被放大</text>
<!--画图-->
<svg viewBox="0 0 100 100"
width="140" height="180"
preserveAspectRatio="xMidYMax slice"
>
<rect x="0" y="0" width="100%" height="100%" fill="blue">
<title>viewBox区域(实际为180*180)</title>
</rect>
<use href="#face" />
</svg>
</g>
<g transform="translate(10 320)">
<text x="70" y="25" transform="rotate(90 80 28)">60</text>
<text x="30" y="75">80</text>
<text x="0" y="95">width>height且</text>
<text x="0" y="115">viewBox被缩小</text>
<!--画图-->
<svg viewBox="0 0 100 100"
width="80" height="60"
preserveAspectRatio="xMidYMax slice"
>
<rect x="0" y="0" width="100%" height="100%" fill="blue">
<title>viewBox区域(实际为80*80)</title>
</rect>
<use href="#face" />
</svg>
</g>
<g transform="translate(235 300)">
<text x="65" y="40" transform="rotate(90 75 43)">90</text>
<text x="20" y="105">60</text>
<text x="0" y="125">width<height且</text>
<text x="0" y="145">viewBox被缩小</text>
<!--画图-->
<svg viewBox="0 0 100 100"
width="60" height="90"
preserveAspectRatio="xMidYMax slice"
>
<rect x="0" y="0" width="100%" height="100%" fill="blue">
<title>viewBox区域(实际为90*90)</title>
</rect>
<use href="#face" />
</svg>
</g>
<text x="80" y="500">preserveAspectRatio = "xMidYMax slice"</text>
</svg>
</div>
在这幅图中,原脸是按照viewBox宽高比为100 : 100的设计图绘制,前两个板块中的脸分别被放大1.6倍和1.8倍,后两个板块中的脸分别缩小为0.8倍和0.9倍;蓝色区域是viewBox经缩放后在视口中显示的区域,多出的海蓝色部分以及脸的轮廓属于viewBox在视口外不可见区域。可以看到,在缩放方式设置为slice的时候,viewBox经过缩放会把视口包含其中,它的缩放率等于max{ 视口的width/viewBox的width,视口的height/viewBox的height };当对齐方式设置为xMidYMax时,viewBox的中点x值与视口的中点x值对齐,viewBox的<min-y>+<height>值与视口的最大y值对齐,这相当于viewBox在视口的底部居中。
3、
细节
①在viewBox定义的区域之外画图也是有效的,但一般不建议这样做。以下有一个案例:
<div style="
width: 200px;
height: 260px;
margin: 40px;
border: 1px dashed gray;
box-sizing: content-box;
">
<svg viewBox="0 0 200 260"
xmlns="http://www.w3.org/2000/svg"
>
<rect x="30" y="30" width="140" height="200" fill="yellow"></rect>
<text x="160" y="120" transform="rotate(90 170 123)">200</text>
<text x="90" y="245">140</text>
<svg viewBox="-10 -10 100 100"
width="140" height="200"
x="30" y="30"
preserveAspectRatio="xMaxYMax meet"
>
<rect x="-10" y="-10" width="100" height="100" fill="red">
<title>viewBox区域(实际为140*140)</title>
</rect>
<rect x="-20" y="-70" width="30" height="30" fill="green">
<title>viewBox外部区域</title>
</rect>
<rect x="30" y="-30" width="30" height="30" fill="green">
<title>viewBox外部区域</title>
</rect>
</svg>
</svg>
</div>
如图所示,黄色部分是140*200的视口区域,红色部分是缩放后面积为140*140的viewBox区域;两块绿色部分有一块完全在viewBox外另一块有一部分在viewBox外,超出部分都是可以被画出来的。
②关于svg的多层缩放,这里有个案例:
<div style="
width: 300px;
height: 300px;
margin: 40px;
border: 1px dashed gray;
box-sizing: content-box;
">
<!--外层svg-->
<svg viewBox="0 0 250 200"
width="300" height="300"
xmlns="http://www.w3.org/2000/svg"
preserveAspectRatio="xMidYMax meet"
>
<rect x="0" y="0" width="100%" height="100%" fill="#aaaaaa" />
<rect x="10" y="10" width="200" height="150"
fill="none" stroke="yellow" stroke-dasharray="4 2" />
<!--内层svg-->
<svg viewBox="0 0 100 100"
width="200" height="150"
x="10" y="10"
preserveAspectRatio="xMaxYMax meet"
>
<rect x="0" y="0" width="100%" height="100%" fill="red" />
</svg>
<text x="220" y="90" transform="rotate(90 235 90)">200*1.2</text>
<text x="130" y="190">250*1.2</text>
<text x="200" y="80" transform="rotate(90 215 80)">150*1.2</text>
<text x="40" y="175">200*1.2</text>
<text x="180" y="70" transform="rotate(90 190 70)">100*1.5*1.2</text>
<text x="110" y="155">100*1.5*1.2</text>
</svg>
</div>
这幅图中,黑色虚线框是300*300的外层svg视口范围,灰色区域是它的viewBox的范围(原来是250*200),根据preserveAspectRatio设置的meet缩放方式,其缩放率为1.2,最终可得范围300*240。内层svg属于外层svg的直接子元素,它的视口在外层svg的viewBox中会缩放,缩放率也是1.2,又根据它的属性width="200"、height="150"、x="10、y="10",则它的最终范围是240*180,偏离外层svg的viewBox的左上角12、12。内层svg的viewBox(原来是100*100)的preserveAspectRatio的缩放方式设置为meet,该viewBox相对于内层svg视口(原来是200*150)的缩放率为1.5,从而可得这个viewBox的实际大小为宽100*1.5*1.2、高100*1.5*1.2,最终缩放率为1.5*1.2。由此我们可以总结,对于多层svg嵌套,其中某一层svg的viewBox的最终缩放率等于从当前层viewBox的缩放率开始连乘到最外层viewBox缩放率的乘积。