SVG是一种图片格式。在实际编程工作中,我们会经常遇到需要对svg图片进行同比缩放或者拖拽的场景。而往往svg图片,并不会那么听话。只有理解了svg缩放的原理,我们才能很好的解决此类问题。本篇内容采用一个独特的视角 - 将svg图片视为一个画布(Canvas),来帮助我们更容易的理解相关原理。
一、了解svg中的 Viewport 和 ViewBox
在svg中,空间的概念可以分为 Viewport 和 ViewBox 两个部分。从结构上可以看做一个div的边框和内部。这里,我们把 Viewport 视为相框,把 ViewBox 视为相片可以更好的理解这两个概念。
Viewport
Viewport比较好理解,就是相框的大小,也就是我们平时视觉能够看得到的范围。不管相框里面装着的相片实际有多大,我们最多能看到相片的范围,就相框那么大。在html中,我们可以通过设定 viewport 来调整相框的大小。
ViewBox
ViewBox可以想象成相片的大小。如果相片本身的尺寸和相框(viewport)一样大的时候,看起来不会有什么问题。我们可以从相框中看到一样大的相片。可是,如果相片(viewbox)的尺寸比相框大或者小时,就会麻烦一些。我们必须思考如何在相框中摆放相片,才能够呈现出自己想要的效果。ViewBox 就能够控制相片的大小,还能够控制相片如何在相框中摆放-也就是在相框中的位置。
1 viewport 等于 viewbox 时
通常,这处于svg的初始化阶段。首先,我们可以直接在svg元素上定义 viewport 的宽和高,当然可以用css定义。基本上,这个dom元素就是 viewport 的宽和高,也就是这个相框的宽高。接下来我们可以在svg中定义具体的图形内容,这里以一支鸟为实例:
html
<svg id="svgElement" width="800" height="400">
<g><!-- 鸟的图形 --></g>
</svg>
此时会得到如下的一个 viewport:
下面,我们可以设定 viewBox
, viewBox 的设定可以写在 SVG 的标签上,设定的属性包含 <min-x>
<min-y>
<width>
<height>
这四个,也就是 viewBox = '<min-x> <min-y> <width> <height>'
。
我们刚刚有提到,ViewBox 中除了能够控制的相片的大小之外,还能够控制相片要如何摆放在相框中,其中 <min-x>
和 <min-y>
就是在控制相片要如何摆放在相框中,而 <width>
和 <height>
则是相片的大小。
默认的情況下,viewBox 的尺寸大小会和 viewport 一樣,因此当我们把一只鸟的相片放入 svg 后,它会自动填满整个相框,两个的大小会是一样的,html代码会像这样:
html
<svg id="svgElement" width="800" height="400" viewBox="0 0 800 400">
<g><!-- 要定义的图形内容 --></g>
</svg>
相片会完整的融入在相框中:
viewBox的初始化尺寸会和viewport一样大小。
2 viewbox 小于 viewport 时: 图像会被放大
通过 viewBox 的设定,我们可以进一步放大或者缩小相片,这里有一个原则是:viewBox会自动的尽可能的去填满viewport的大小,说起来有点抽象,假设一开始我们的 viewport 宽高为: 800x400,现在我们可以通过 viewBox 的设定,使得 viewBox的大小变为 400x200,html代码如下:
html
<svg id="svgElement" width="800" height="400" viewBox="0 0 400 200">
<g><!-- 鳥的圖形 --></g>
</svg>
当相片的大小设定比相框小时,它会在原本的相片上裁剪一小块区域(这里是 400x200),接着把它调整到填满整个viewport的大小(800x400),分解过程如下:
- 第一步:设定比 viewport 小的 viewBox
- 第二步:剪裁
- 第三步:填满
此时,会产生一个神奇的现象,虽然我们的相片尺寸(viewBox)比原本的要小,但是因为它会自动的去填满整个 viewport 的缘故,所以实际上相片被放大了。
3 viewbox 大于 viewport 时: 图像会被缩小
同样的原理,当我们设定的 viewBox 大于 viewport时,它会先把这张相片放大到指定的尺寸(比如我们将viewBox设定为1600x800),然后再尽可能的塞入 viewport 中去(这里是 800x400),html代码如下:
html
<svg id="svgElement" width="800" height="400" viewBox="0 0 1600 800">
<g><!-- 鳥的圖形 --></g>
</svg>
viewBox 设定为1600x800,比原本的 viewport 大。照片的底图会被放大,但是图案大小不会变。
将1600x800的 viewBox,塞进 800x400 的viewport当中,就会造成图案变小的情况。
所以虽然 viewBox 的设定(1600x800) 大于原始viewBox的大小(800x400),但是实际上,照片在显示上却会被缩小。
二、了解 SVG ViewBox位置的设定
上面提过,viewBox的设定,包含了四个属性 <min-x>
<min-y>
<width>
<height>
。在上面的示例中,我们使用了其中后面的2个:<width>
<height>
来控制viewBox的大小。而前两个属性,则是来设定相片的位置。
x代表横向,y代表纵向。当 x>0时,向左移动,反之向右移动。当y>0时向上移动,反之向下移动。
假如我们设定 viewBox的 min-x 为150时,即: viewBox = "150 0 800 400" ,viewBox会向左移动150个单位:
第一步:先移动
第二步:后裁剪。左边超出viewport的部分,会被遮盖,而右边则会空白:
我们也可以同时设定 min-x 和 min-y ,例如 viewBox = "-400 -200 800 400", 会得到这样的效果:
第一步:先移动
第二步:后裁剪。最后效果是这样的:
三、viewBox设定造成的影响
在viewBox的4个属性<min-x>
<min-y>
<width>
<height>
中,我们可以简单的理解成,前两项<min-x>
<min-y>
控制的是位移(translate),能够实现左右移动的效果;后两项 <width>
<height>
控制的是缩放(scale)。
但是因为viewBox实际上影响的是svg当中的坐标系统,所以会和我们的直觉相反。例如,当我们设定 min-x 和 min-y 越大时,实际上看到的画面会往左上方移动。同样,当我们设定width和height越大时,画面实际上看起来会缩小。
四、当viewport和viewBox的尺寸并不是等比例时:preserveAspectRatio
上文提到,当viewBox和viewport的尺寸大小不一样时,viewBox会尽可能的去塞满整个viewport。上面的例子,我们都把viewBox的尺寸大小设定的和viewport是等比的情况,如果是不同比例的情况下,我们该使用怎样的方式来实现对齐和填满呢?
这时,我们就需要用到 preserveAspectRatio 属性了。在这个属性当中,可以设定对齐的方式(align),用什么样的方式填满(meet 或者 slice)
在两者是等比的情况下,preserveAspectRatio 的值对画面没有任何影响,如果对于 preserveAspectRatio 的属性想要有更多的了解,可以参考這篇文章([译]理解 SVG 座標系統與 Transformations @ Andyyou),或進一步参考文章中最后面所列的参考文章。
五、深入理解 SVG 坐标系
为了帮助我们更进一步的探讨 SVG 坐标系统和 viewport 坐标系统,我们要來认识三种不同的座标名称,分别是 offset
、client
和 SVG Point
。
其中 offset 和 client 所取得的值都是属于 viewport 坐标系统中的座标值(也就是 1px 就是真实的 1px),两者的差別在于 offset 是相对于 container 左上角的点,也就是以 container 左上角为(0, 0);而 client 则是相对于 window 左上角(浏览器窗口左上角)的点,以 window 左上角为(0, 0),越右 X 值越大,越下 Y 值越大。
SVG 坐标系统就比较特別一点了,它有专属的坐标点,可以通过 viewport 坐标系的 client 坐标加以转换。
1、 viewBox 等同于 viewport 时
我们用 Sara Soueidan 在所提供的互动案例来做更多說明,一开始的時候,我先把 viewBox 设成和 viewport 一样大,在这种情况下 SVG 昨天系统中的 1 单位大小会和 viewport 的一样大,也就是 1px。留意蓝色的尺标是 SVG 坐标系統、灰色的尺标则是 viewport 坐标系统:
同时我们标下鸟右下角的这个点,让我们后面能够更清楚 SVG 坐标系统的变化。在一开始的时候,因为 viewport 等同于 viewBox 的缘故,所以 SVG 坐标系统中这个点,和 viewport 坐标系统中的 offset 坐标点,都会是(200, 300):
2、 viewBox是viewport尺寸的一半时
接着,未能可以看到,当我把 viewBox 设为 viewport 的一半(viewBox = "0 0 400 300"
),也就是让鸟看起來变 2 倍大时,这时候 SVG 坐标系统中的 1 单位,会变成 viewport 的 2 单位(这里就是 2px)。
但要留意的是,虽然图案被放大了两倍,但是鸟右下角这个在蓝色尺标的 SVG 坐标系统中一样是(200, 300),可是灰色尺标的 viewport 坐标系统中的 offset 坐标点会变成两倍,也就是(400, 600)。
3、当viewBox是viewport尺寸的两倍大时
同样的道理,当我把 viewBox 设为 viewport 的 2 倍大(viewBox = "0 0 1600 800"
),也就是让鸟看起來变 2 倍大时,这时候 SVG 坐标系统中的 1 单位,会变成 viewport 的 1/2 单位(这里就是 0.5px)。
这时候你会看到,虽然蓝色尺标的 SVG 坐标系统中,右下角坐标仍然是(200, 300),但是 viewport 坐标系统中的 offset 坐标点则变成了(100, 150)。
总结
- 知道有 viewport 和 SVG 这两种不同的坐标系統
- 知道有 offset, client, SVGPoint 这三种不同的坐标点
- 知道有 offset 和 client 属于 viewport 坐标系统;SVGPoint 属于 SVG 坐标系统
- 知道通过 viewBox 的设定可以达到缩放和移动的效果
参考资料
- Understanding SVG Coordinate Systems and Transformations
- [中译]理解 svg 坐标系统和 transformation @ Andyyou
- SVG 研究之路 (23) - 理解 viewport 与 viewbox @ OXXO
- 图片来源 @ TeamTreehouse