简介svg的核心能力——矢量缩放

大家都知道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-xmin-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&gt;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&lt;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&gt;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&lt;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&gt;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&lt;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&gt;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&lt;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缩放率的乘积

  • 37
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值