图解SVG的核心概念

发布于 大漠

在SVG中,对于很多初学者或者使用SVG有一段时间的同学来说,有几个概念总是会相互混淆。比如,SVG的画布视窗(Viewport)ViewBox等。这几个概念对于学习或掌握SVG来说是非常必要的。在此我们一起来学习和了解这几个概念。

画布

SVG和Canvas类似,它也有一个画布的概念。SVG中的画布是一个无限区域,即x轴和y轴都可以无限延伸。好比Sketch中,新创建的一个文件,你可以在新创建Sketch设计软件中的可见区域或不可见区域绘制任何图形(在Sketch可以通过拖跩让不见区域到可见区域):

如果回到SVG中的话,那么<svg>标签就是一个画布,这个画布大小是无限的。

<svg></svg>

SVG的初始坐标

SVG也有一个初始坐标,这个坐标和我们所理解的Web坐标相似,都在屏幕的左上角。如果放到SVG画布中的话,这个坐标原点是在画布的正中间(即(0,0)位置),沿着x轴向右的水平方向为正值方向,沿着y轴向下的方向为正值方向:

我们可以在这个坐标系统中(SVG画布中)任意位置绘制想要的图形,比如:

视窗(Viewport)

SVG画布是可以无限延伸的,但我们的设备屏幕本身并不是无限长的,于是SVG的渲染输出是必须对应有限尺寸的可视区域。这个可视区域将它称为视窗,即Viewport。这个可视区域指的就是SVG的显示范围。我们可以把Viewport想成浏览器视窗。

我们通过视窗来看里面的内容。

在SVG中,也可以把视窗当作一个容器,即创建了一个<svg>标签就创建一个视窗。而这个视窗的大小可以通过<svg>widthheight属性来指定大小:

<!-- 创建一个800 x 600 个单位的Viewport -->
<svg width="800" height="600">
    <!-- SVG画布 -->
</svg>

注意,这里所说的一个单位指的是用户单位。比如上面示例中的800 x 600并不是我们所说的px单位,如果用户的坐标系统用的是cm作为单位,那么对应的是800cm x 600cm

画布 vs 视窗

简单地说,如果你使用<svg>标签就同时创建了一个SVG画布和一个视窗。这两个概念往往是很容易混淆的。因为它们两各自独立却又相互关联。因此,我们很有必要明白它们之间的关系。

为了更好的理解这两个抽象的概念,用生活中的一个实例来阐述。你可以把视窗想象成飞机上的窗户,把画布想象成窗外海阔天空的风景。坐在机舱的你只能透过窗户观看窗外的风景。

当然,SVG的画布和视窗并不是完全割裂的,它们之间有着一定的关联:

  • 每创建一个<svg>元素,就相当于创建了一个无穷大的画布,同时创建了一个有限区域的视窗
  • SVG的画布和视窗分别对应两个坐标系统,一个用户坐标系统(建立在SVG画布上的坐标系统),一个视窗坐标系统(建立在视窗上的坐标系统),这两个坐标系统默认是对齐的,它们的原点和坐标都是完全一致的,也就是说初始用户坐标系统的原点就位置视窗的左上角(也就是x轴正向向右,y轴正向向下)

SVG的viewBox

SVG中还有另一个概念,那就是viewBox的概念。同样拿生活中的示例来向大家阐述SVG中的viewBox。在坐飞机的时候,你可能会用手机透过飞机的窗户(Viewport)来拍摄窗外风景(SVG画布),那么这个相机有一定的可视区域,这个区域就类似于SVG中的viewBox

拿着手机你可以在窗口区域(Viewport)内移动,这个时候只有手机区域(摄像机镜头)才可见,而且随着你移动,它的可见区域会变动。换到SVG的<svg>标签中的话,它由四个参数来决定:

<svg viewBox="0 0 200 200">
</svg>

viewBox的值是:

viewBox = "<min-x> <min-y> width height"

其中<min-x><min-y>的值决定viewBox的左上角顶点坐标,widthheight则决定viewBox的区域大小。

如果拿到Sketch中来的话,整个绘制工作区相当于SVG的画布,我们可一定的可视区域当作是SVG的视窗,而它的“画板”工具可以看作是SVG的viewBox。只有在“画板”区域内的图形才能被看见:

SVG画布、视窗和viewBox的关系

我们用一个示例来描述SVG世界中画布视窗 和 **viewBox**三者之间的关系。

简单地说,在SVG的世界中,底下有一张不限宽高的画布,好比上图中的浏览器可视区域,预设用px当单位,我们在这个SVG画布中绘制图形;接着上面有一层SVG视窗(Viewport),好比上图中的相框;接着再有一层viewBox,好比上图中的风景图(相片)。

正如前面提到的,SVG画布是无限大的(想象成上图中的浏览器可视区域可以不断扩展大小);SVG视窗是有一定大小的,比如上图中的相框大小(相框的宽和高决定了其大小),这也是你眼睛看得到的范围。也就是说,不管你的相片有多大(viewBox),你能看到的实际范围就是相框的大小。

viewBox则可以想成相片的大小,而这张相片可能:

  • 相片可能会比相框小
  • 相片可能会比相框大
  • 相片可能会和相框一样大

如果相片和相框一样大的时候,你可以在相框中看到完整的相片。可相片和相框大小不同时,就会相对麻烦一些,我们要控制相片在相框中的位置和大小,才能在相框中呈现你想要呈现的东西。可以使用<svg>viewBoxwidthheight来控制相片大小,使用<min-x><min-y>控制相片的位置。

视窗(相框)和viewBox(相片)相等

我们来看一个示例:

<svg width="800" height="400" viewBox="0 0 800 400">
    <image href="https://static.fedev.cn/sites/default/files/blogs/2020/2008/svg-in-react-32.png" width="800" height="400" />
</svg>

示例中的<svg>widthheight创建了一个视窗(Viewport),它的大小是800 x 400,也就是相框的宽高。而viewBox属性创建了一个ViewBox。这个ViewBox除了能够控制相片大小之外,还能控制相片如何在相框中摆放,其中viewBox的前两个值<min-x> <min-y>(即0 0)就是控制相片要如何摆放在相框中,而<width> <height>则控制相片的大小。

预设的情况之下,ViewBox和Viewport大小一样,因此当我们把相片放到相框中,会自动填满相框:

视窗(相框)大于 viewBox(相片)

我们可以通过<svg>viewBox属性来调整ViewBox(相片)大小,这里有一个原则是,ViewBox会自动尽可能去填满整个Viewport。

<svg width="800" height="400" viewBox="0 0 400 200">
    <image href="https://static.fedev.cn/sites/default/files/blogs/2020/2008/svg-in-react-32.png" width="800" height="400" />
</svg>

这个时候视窗大小是800 x 400,而ViewBox的大小是400 x 200。即ViewBox(相片)比视窗(相框)小,它会在原本相片上裁切一小块(ViewBox区域),接着把它调整到填满整个视窗。

我们用几张图来分解这个过程。在800 x 400的视窗上设置了400 x 200的ViewBox:

相片会按ViewBox的大小裁切:

最后ViewBox会尽可能的填满整个视窗,图片放大:

视窗(相框)小于 viewBox(相片)

同样的使用viewBox将ViewBox(相片)设置比视窗(相框)大:

<svg width="800" height="400" viewBox="0 0 1600 800">
    <image href="https://static.fedev.cn/sites/default/files/blogs/2020/2008/svg-in-react-32.png" width="800" height="400" />
</svg>

这个时候viewBox会让ViewBox(相片)变大(放大到1600 x 800),但相片大小不变:

ViewBox比视窗大,但要让相片塞到相框中,相片就会缩小放到相框中:

SVG的ViewBox位置设定

上面的示例,我们看到的都是使用viewBox来控制相片大小的。如果你想调整相片在相框中的位置,可以使用viewBox中的另两个参数<min-x><min-y>,比如下面这个示例:

<svg width="800" height="400" viewBox="-100 -100 1600 800">
    <image href="https://static.fedev.cn/sites/default/files/blogs/2020/2008/svg-in-react-32.png" width="800" height="400" />
</svg>

你将看到的效果如下:

从效果上可以看出,相片(ViewBox)的<min-x><min-y>设置的值都为-100,ViewBox沿着x轴向右移动了100个单位,同时沿着y轴向下移动了100个单位。

再来看另外一个示例,<min-x><min-y>都是正值:

<svg width="800" height="400" viewBox="300 300 1600 800">
    <image href="https://static.fedev.cn/sites/default/files/blogs/2020/2008/svg-in-react-32.png" width="800" height="400" />
</svg>

现在ViewBox(相片)有一部分移出了Viewport(相框)区域:

从这两个示例,我们可以发现:

  • <min-x>的值为正值时,ViewBox会沿着x轴向左移动;当其值为负值时,ViewBox会沿着x轴向右移动
  • <min-y>的值为正值时,ViewBox会沿着y轴向上移动;当其值为负值时,ViewBox会沿着y轴向下移动

你可能已经发现了,在viewBox属性的四个参数中,其中<min-x>相当于CSS中的translateX()<min-y>相当于CSS的translateY(),两者结合起来就类似于translate(),主要用来对ViewBox进行移动;而widthheight有点类似于CSS的scale(),主要对ViewBox进行缩放。

由于viewBox实际影响的是SVG当中的坐标系统,所以从直觉上来看的话,会有一种相反的感觉。当<min-x><min-y>的值越大时,ViewBox的越往左上方移动;同样的,当widthheight越大时,看到的画面就小。

为了更好的让大家能理解SVG的Viewport和ViewBox之间的关系,我复制了@wattenberger一个Demo,大家可以尝试着改变示例中的参数,查看viewBox和视窗之间的变化:

SVG的preserveAspectRatio

前面花了一定的篇幅和大家一起探讨了ViewBox(相片)和 Viewport(相框)之间的关系,不难发现很多时候ViewBox和Viewport有不同大小的尺寸。在尺寸大小不一样时,SVG的ViewBox会尽可能的去填满整个Viewport,前面已经通过示例向大家演示过了。或许你已经发现了,前面阐述ViewBox和Viewport不同尺寸时两者关系的示例,有着一个共同特点,那就是ViewBox和Viewport的宽高比是等比的

那么问题来了,如果ViewBox和Viewport的宽高比不是相同的,ViewBox(相片)又要用什么样的方式来对齐和填满Viewport(相框)?这个时候就需要用到<svg>的另一个属性 preserveAspectRatio

接下来,我们来看一下preserveAspectRatio如何使用,以及其作用。

W3C的规范是这样描述preserveAspectRatio

preserveAspectRatio属性表示是否强制进行统一缩放。适用于所有建立新SVG视窗(Viewport)的元素以及<image><marker><pattern><view>元素

需要特别提出的是,preserveAspectRatio只适用于在相同元素(除了<image>)上为viewBox提供了值的情况。对于这些元素,如果没有提供属性viewBox,则忽略preserveAspectRatio。对于 <image> 元素, preserveAspectRatio 指示引用的图像应该如何与参考矩形进行匹配,以及是否应该相对于当前用户坐标系保留参考图像的长宽比。

我们再来看看preserveAspectRatio属性的实际使用。preserveAspectRatio属性接受两个值:

preserveAspectRatio = <align> <meetOrSlice>?

其初始值为**xMidYMid meet**。其中:

<align> = none | xMinYMin | xMidYMin | xMaxYMin | xMinYMid | xMidYMid | xMaxYMid | xMinYMax |xMidYMax | xMaxYMax
<meetOrSlice> = meet | slice
  • <align>参数指示是否强制统一缩放,如果是,当ViewBox(相片)宽高比和Viewport(相框)的宽高比不同时使用的对方方法。
  • <meetOrSlice> 是可选的,用来指定图像缩放方式

在实际代码中,我们可以像下面这样使用:

<svg width="800" height="400" viewBox="0 0 400 200" preserveAspectRatio="xMidYMid">
    <rect x="0" y="0" width="400" height="200" fill="#9090fa"></rect>
</svg>

<!-- 或者 -->
<svg width="800" height="400" viewBox="0 0 400 200" preserveAspectRatio="xMidYMid meet">
    <rect x="0" y="0" width="400" height="200" fill="#9090fa"></rect>
</svg>

对于很多同学,可能更关注的是<align><meetOrSlice>对应参数具体解释和所起的作用。

<align>

preserveAspectRatio属性的第一个参数<align>的值除了none之外的每个值都是有由两个部分组成,即x轴和y的对齐方式的组合。比如xMidYMid就是xMid + YMid,其中xMid用来描述ViewBox(相片)在Viewport(相框)中x轴方向的对齐方式,YMid是用来描述ViewBox(相片)在Viewport(相框)中y轴方向的对齐方式。x轴和y轴每个方向都有三个值,具体代表的意思如下:

x轴的值 含义 y轴的值 含义
xMin ViewBox和Viewport左边缘对齐(水平居左) YMin ViewBox和Viewport顶部边缘对齐(垂直居上)
xMid ViewBox和Viewport在x轴中心点对齐(水平居中) YMid ViewBox和Viewport在y轴中心点对齐(垂直居中)
xMax ViewBox和Viewport右边缘对齐(水平居右) YMax ViewBox和Viewport底部边缘对齐(垂直居下)

我们用张图来描述:

有一点需要声明:preserveAspectRatio的第一个参数的值是xy轴组合物,因此在实际使用的时候,xy轴的独立物是不存在的。即preserveAspectRatio="xMin"preserveAspectRatio="YMin"是无效值

也就是说,preserveAspectRatio第一个参数<align>的值是通过x(三种)和y(三种)可以组合出个不同的值。

为了更好的理解<align>中值的含义,这里上一张图,用来描述ViewBox和Viewport之间的几个轴的关系:

有了上图之后,<align>中的描述会更易于理解一些:

<align> 含义 备注
xMinYMin ViewBox的min-x和Viewport的x轴最小值对齐;ViewBox的min-y和Viewport的y轴的最小值对齐 表现行为类似于CSS中background-position: 0% 0%
xMidYMin ViewBox的mid-x和Viewport的x轴中间点对齐;ViewBox的min-y和Viewport的y轴的最小值对齐 表现行为类似于CSS中background-position: 50% 0%
xMaxYMin ViewBox的min-x + width和Viewport的x轴的最大值对齐;ViewBox的min-y和Viewport的y轴的最小值对齐 表现行为类似于CSS中background-position: 100% 0%
xMinYMid ViewBox的min-x和Viewport的x轴的最小值对齐;ViewBox的mid-y和Viewport的y轴的中间点对齐 表现行为类似于CSS中background-position: 0% 50%
xMidYMid ViewBox的mid-x和Viewport的x轴的中间点对齐;ViewBox的mid-y和Viewport的y轴的中间点对齐 表现行为类似于CSS中background-position: 50% 50%
xMaxYMid ViewBox的min-x + width和Viewport的x轴的最大值对齐;ViewBox的mid-y和Viewport的y轴的中间点对齐 表现行为类似于CSS中background-position: 100% 50%
xMinYMax ViewBox的min-x和Viewport的x轴的最小值对齐;ViewBox的min-y + height和Viewport的y轴的最大值对齐 表现行为类似于CSS中background-position: 0% 100%
xMidYMax ViewBox的mid-x和Viewport的x轴的中间点对齐;ViewBox的min-y + height和Viewport的y轴的最大值对齐 表现行为类似于CSS中background-position: 50% 100%
xMaxYMax ViewBox的min-x + width和Viewport的x轴的最大值对齐;ViewBox的min-y + height和Viewport的y轴的最大值对齐 表现行为类似于CSS中background-position: 100% 100%

最后用一张图向大家展示<align>不同值的效果:

<meetOrSlice>

<meetOrSlice>preserveAspectRatio属性的第二个值,它主要由meetslice两个值。其主要作用是:

控制ViewBox是否要在Viewport中完整展示

不管是meet还是slice他有一个共同原则:ViewBox需要保持宽高比进行缩放。为了更好的讲清楚meetslice,我有必须花一点点时间来阐述一下ViewBox宽高比的概念。比如下面这样的一个示例:

<svg width="800" height="400" viewBox="0 0 400 200">
</svg>

从示例中的viewBox的值我们可以得知,ViewBox的width400个单位,height200个单位,这个时候其宽高比是2

r = width / height = 400 / 200 = 2

如果我们对ViewBox进行缩放,比如说, ViewBox放大到宽度为600,高度为300,此时ViewBox的宽高比也是2,那么它就是属于一个保持宽高比的放大。如果ViewBox放大到宽度为600,高度为200,此时ViewBox的宽高比就变成了3,那么它就不属于保持宽高比的放大。

有了这个概念之后,我们来看meetslice是如何让ViewBox缩放。

meet

meet<meetOrSlice>的默认值,ViewBox将缩放到:

  • ViewBox保持宽高比进行缩放
  • 整个ViewBox在Viewport内可见
  • 尽可能放大ViewBox,同时仍然也满足其他的条件

我们用示例来描述meet

正如上图所示,ViewBox的宽高比是2400 ÷ 200 = 2),并且将ViewBox放到两个不同的Viewport中(第一个是宽大于高,第二个是高大于宽),然后将ViewBox分别在不同的Viewport中进行缩放,分别做了三个不同的缩放处理:

  • 1”号位ViewBox没有按宽高比进行缩放,不符合条件
  • 2”号位ViewBox虽然按宽高比进行缩放,但溢出了Viewport,也不符合条件
  • 3”号位ViewBox才是符合meet,既符合按照ViewBox的宽高比进行缩放,而且整个ViewBox在Viewport中可见

slice

我们接着来看<meetOrSlice>的第二个参数值slice。该值会将ViewBox缩放到:

  • ViewBox保持宽高比进行缩放
  • 整个Viewport将覆盖ViewBox
  • ViewBox将会被尽可能的缩小,但是仍然符合其他标准

同样用一个简单的示例来描述slice

正如上图所示,ViewBox的宽高比是2400 ÷ 200 = 2),并且将ViewBox放到两个不同的Viewport中(第一个是宽大于高,第二个是高大于宽),然后将ViewBox分别在不同的Viewport中进行缩放,分别做了三个不同的缩放处理:

  • 1”号位ViewBox没有按宽高比进行缩放,不符合条件
  • 2”号位ViewBox虽然按宽高比进行缩放,但未填满整个Viewport,也不符合条件
  • 3”号位ViewBox才是符合slice,既符合按照ViewBox的宽高比进行缩放,而且ViewBox填满了整个Viewport

你是不是也发现了,<meetOrSlice>的渲染行为和CSS中的background-size取值为containcover非常的类似,其中meet的行为类似于background-size: containslice的行为类似于background-size: cover

结合前面的介绍,<svg>preserveAspectRatio属性中的<align>不是none(取值为none<meetOrSlice>自动失效),那么preserveAspectRatio的呈现行为就和background-position / background-size类似。

阅读到这里,你应该对preserveAspectRatio有一定的了解:

preserveAspectRatio<align><meetOrSlice>可以设定ViewBox在Viewport中的缩放与定位行为。可以决定ViewBox在Viewport中全部显示还是只显示局部

在这里,我复制了@Sara Soueidan一个Demo,大家可以通过改变<svg>viewBox属性的各个参数的值和调整<svg>preserveAspectRatio属性的参数值,来查看具体的效果:

虽然说preserveAspectRatio的取值可以用来设置ViewBox在Viewport的位置和缩放方式,但有的时候也取决于viewBoxwidthheight。比如,我们在上面的Demo上,将viewBoxwidthheight分别调整到200300,并且<min-x><min-y>设置为0,同时将preserveAspectRatio<meetOrSlice>设置为meet

然后去调整preserveAspectRatio<align>的值,将会发现不同的值也会达到一样的结果:

如果我们把<meetOrSlice>的值换成slice,结果就完全不同了:

因为slice设定下,ViewBox要保持固定宽高比例(200 ÷ 300 = 0.6667),ViewBox要填满整个Viewport的区域(800 x 600),因此ViewBox在x轴从200个单位被换算到了Viewport的800个单位(也可以简单的理解,ViewBox水平方向从200放大到800),这个时候ViewBox的width就是800个单位。为了满足slice的第一个标准,ViewBox缩放需要保持宽高比,那么这个时候ViewBox的height就需要从300个单位换算到1200个单位(800 ÷ height = 200 ÷ 300,换算出来height = 1200)。那么ViewBox的height就比Viewport的height更大,ViewBox将溢出Viewport,而且溢出的部分将被裁切。

如果你想体验viewBoxpreserveAspectRatio给SVG带来的不同变化,可以尝试着在上面的Demo上调整不同的参数值,查看相关的渲染结果

深入了解SVG坐标系

从上面的内容我们可以看出,当<svg>元素显式设置viewBox属性时,SVG将会有很多特别的效果。比如SVG元素的移动,缩放,扭曲等。这主要是,有了viewBox之后,SVG中的坐标系统也会随之改变

前面我们提到过:SVG画布使用的是用户坐标,SVG视窗(Viewport)使用的是视窗坐标,而且视窗坐标只是用来作最后渲染的参考换算。新增的viewBox却可以用来调整用户坐标(也可以将其理解为最终坐标系)。在SVG中,viewBox的坐标系统可以比视窗坐标大,也可以比视窗坐标小。

我们也可以这样来理解,在SVG中通过在<svg>标签上设置widthheight的值,来指定视窗坐标,一旦视窗坐标系统初始化,浏览器预设会建立一个用户坐标系统(SVG画布坐标系统),其所有定义跟视窗坐标一样(SVG画布坐标系统和视窗坐标系统相同)。

后续我们可以使用<svg>viewBox属性来调整用户坐标系统,如果用户坐标系统的宽高比和视窗一致,就会自动扩展以填满视窗的空间。如果用户坐标系统和视窗比例不同,就需要使用<svg>中的另一个属性preserveAspectRatio属性来调整“用户坐标系统”在视窗中显式方式。

也就是说,在SVG世界中,掌握ViewBox和Viewport,其实就是在处理这两个坐标系统!为了更好的帮助大家深入了解这两个坐标系统,依旧借助@Sara SoueidanDemo渲染结果来向大家呈现。

先上图:

为了更好的呈现,将<svg>viewBox<min-x><min-y>的值调整为-100个单位,其对应的代码就是:

<svg width="800" height="800" viewBox="-100 -100 800 600">
</svg>

在图中可以看到两个坐标系统,一个是灰色的(对应的是Viewport坐标系统),一个是蓝色的(对应的是ViewBox坐标系统)。

  • Viewport坐标系也称为视窗坐标系,在这个坐标系统中,1px就是我们熟悉的1px,而且这个坐标系统是相对固定的
  • ViewBox坐标系也称为SVG坐标系用户坐标系,在这个坐标系统中,值不一定是有单位的,如果没有显式指定单位,预设单位会以Viewport的单位为单位(例如px

其中ViewBox坐标系和Viewport坐标最大的区别是:ViewBox坐标系统中1单位大小不一定是1px。我们来简单地看几个这方面的示例。

假设ViewBox和Viewport大小等同:

<svg width="800" height="600" viewBox="0 0 800 600">
</svg>

这个时候,ViewBox坐标系统中的1单位大小会和Viewport坐标系统中的一样大,即**1px**。比如下图所示,留意蓝色和灰色的尺标:

为了更好的向大家说清楚ViewBox坐标系统的变化,可以把上图中鸟的右下角标注出来:

接着,把viewBox的值调整为viewBox="0 0 400 300"(ViewBox相当于Viewport的一半):

从上图中可以发现那只鸟看起来放大了两倍,蓝色的尺标也看上去放大了两倍。鸟的右下角那个点也变了:

鸟右下角顶点坐标,在ViewBox坐标系统(蓝色标尺)对应的还是(200, 300),但在Viewport坐标系统(灰色标尺)对应的就变成了(400, 600)。这个时候,ViewBox坐标系统中的1单位对应的就是Viewport坐标系统的2单位(在这个示例中就是2px

再来看另一个场景,将viewBox属性的值调整为viewBox="0 0 1600 1200"(ViewBox相当于Viewport的两倍):

从上图中可以发现那只鸟看起来缩小一半,蓝色的尺标也看上去缩小了一半。鸟的右下角那个点也变了:

鸟右下角顶点坐标,在ViewBox坐标系统(蓝色标尺)对应的还是(200, 300),但在Viewport坐标系统(灰色标尺)对应的就变成了(100, 150)。这个时候,ViewBox坐标系统中的1单位对应的就是Viewport坐标系统的0.5单位(在这个示例中就是0.5px

上面三个示例中的viewBox<min-x><min-y>都是0。经过前面的学习,viewBox的值改变对于ViewBox系统的变化是多样的。比如,viewBox的值从0 0 800 600换成-100 -100 800 600,ViewBox坐标系统和Viewport坐标系统就不同:

从上图中可以看出,ViewBox坐标系统原点从(0, 0)移到了(-100, -100)(对应Viewport坐标系统中的(100, 100)),看上去整个ViewBox坐标系统在Viewport系统中做了一个位移(向右移了100,向下移了100)。由于ViewBox和Viewport一样大,在该示例中,ViewBox坐标系的1单位和Viewport坐标系的1单位相同(在该示例中就是1px)。

但鸟的右下角顶点坐标是有变化的:

鸟右下角顶点坐标在ViewBox系统中(蓝色标尺)是(200, 300),对应的Viewport坐标系统中(灰色标尺)是(300, 400)

你可以尝试着调整不同的viewBox属性的值,查看不同的效果,比如viewBox="-100 -100 400 300":

在该示例中,ViewBox坐标系统中的1个单位相当于Viewport坐标系统中的2个单位(该示例是2px),因此你会发现,ViewBox坐标系统在Viewport坐标系统中向右向下移了200个单位。反之,换成viewBox="-100 -100 1600 1200"时,ViewBox坐标系统在Viewport坐标系统中只向右向下移了50个单位:

特别声明,如果ViewBox的宽高比和Viewport宽高比不等同时,ViewBox坐标系统和Viewport坐标系统会变得更为复杂,这里不做阐述,感觉兴趣的话,可以在Demo中调整相应参数,但看效果,加强理解

有关于SVG中这几个核心概念的更多介绍,还可以阅读@Sara Soueidan的《Understanding SVG Coordinate Systems and Transformations: The viewport, viewBox, and preserveAspectRatio》一文。

小结

在SVG的世界中,这几个概念是最核心的,也是最易于让人感到困惑和混淆。如果你对这个概念理解清楚了,那么后续使用SVG都不会再有难度和困惑。