解析SVG 标签的 viewBox属性
案情提要
相信小伙伴们在学习 svg标签
的 viewBox属性
时候,无论是看w3cschool
还是 MDN
的官方文档,都是一头雾水。比如MDN给出的解释:viewBox 属性允许指定一个给定的一组图形伸展以适应特定的容器元素。viewBox 属性的值是一个包含 4 个参数的列表 min-x, min-y, width and height,以空格或者逗号分隔开,在用户空间中指定一个矩形区域映射到给定的元素。
我当时也是尝尽了苦头,绕了很大的弯子才弄懂其中的奥秘。下面我将我的探究成果分享给大家。
先说结论:
话不多说,直接开干
viewBox属性
的值是一组相对的值,
我们首先创建一个svg标签
,然后规定其宽高为300px*300px
(下面我们都用这个大小的svg),绘制其边框,内部再绘制一个矩形,宽高为100px*100px
(这个矩形的宽高也不变)。我们只对viewBox
的值进行改变。
- 设置
viewBox="0 0 300 300"
<svg width="300px" height="300px" viewBox="0 0 300 300" style="border: 1px solid red;"> <rect width="100" height="100" fill="green"></rect> </svg>
这里的矩形dom为其设置的原始大小。
- 现在设置
viewBox="0 0 100 100"
<svg width="300px" height="300px" viewBox="0 0 100 100" style="border: 1px solid red;"> <rect width="100" height="100" fill="green"></rect> </svg>
现在可以发现,第一次矩形渲染出的dom宽度 / 其原始大小 = 第一次svg的width / 第一次viewBox的width = 1
;现在矩形放大到原来的3倍
,此恰好svg的width / viewBox的width = 3
,是不是可以猜想:svg内部的缩放比例就等同于 svg的width / viewBox的width
(现在宽高都设置成比例一致的,后面再设置成不同的来探究)。下面进行验证。
- 设置
viewBox="0 0 150 150"
<svg width="300px" height="300px" viewBox="0 0 150 150" style="border: 1px solid red;"> <rect width="100" height="100" fill="green"></rect> </svg>
现在,svg的width / viewBox的width = 2
,渲染出来的dom节点的宽高变为原来的2倍
,遵循我们的假设。如果我们再放大一些呢(超出svg的大小)? - 设置
viewBox="0 0 50 50"
<svg width="300px" height="300px" viewBox="0 0 50 50" style="border: 1px solid red;"> <rect width="100" height="100" fill="green"></rect> </svg>
现在,svg的width / viewBox的width = 6
,渲染出来的dom节点的宽高变为原来的6倍
,遵循我们的假设。那如果我们将渲染出来的图形进行缩小呢? viewBox="0 0 600 600"
<svg width="300px" height="300px" viewBox="0 0 600 600" style="border: 1px solid red;"> <rect width="100" height="100" fill="green"></rect> </svg>
同样的,svg的width / viewBox的width = 0.5
,渲染出来的dom节点的宽高变为原来的0.5倍
,遵循我们的假设。事实证明我们的假设是成立的。
现在呢,我们想到,这个属性有四个参数,但是我们使用控制变量法,前面两个参数为0,只考虑了后面两个参数,那么前面两个参数的作用是什么呢?
追踪——前面两个参数的作用
前提:我们将原先元素的宽高设置保持不变,只改变viewBox的值。
- 设置
viewBox="50 50 100 100"
<svg width="300px" height="300px" viewBox="50 50 100 100" style="border: 1px solid red;"> <rect x="0" y="0" width="100" height="100" fill="green"></rect> </svg>
这时候我们会发现,渲染的dom大小好像跟之前 viewBox="0 0 100 100"
时候的dom大小一致,而且在svg元素中的部分是整个dom的1/4,那么这种位置的变化规律是什么呢?我们多试几次再总结。
- 设置
viewBox="50 50 150 150"
<svg width="300px" height="300px" viewBox="50 50 150 150 style="border: 1px solid red;"> <rect x="0" y="0" width="100" height="100" fill="green"></rect> </svg>
这次我们同样看到,在svg元素中的部分是整个dom的1/4,而且渲染出的dom大小仍然遵循之前的规律。这是个巧合吗?那我们再试一组。 viewBox="50 50 200 200"
<svg width="300px" height="300px" viewBox="50 50 200 200" style="border: 1px solid red;"> <rect x="0" y="0" width="100" height="100" fill="green"></rect> </svg> ~~~
这次我们得到同样的结果,在svg元素中的部分是整个dom的1/4,而且渲染出的dom大小仍然遵循之前的规律。所以我们可以知道,viewBox的前面两个参数对缩放的倍数无影响。此时我们改变前两个参数试试。
viewBox=" 50 100 300 300"
<svg width="300px" height="300px" viewBox="50 100 300 300" style="border: 1px solid red;"> <rect x="0" y="0" width="100" height="100" fill="green"></rect> </svg>
这里我们发现,第一个参数控制横向移动的距离,第二个参数控制纵向的移动距离,正方向为左、上。那么移动的距离是如何计算的呢?我们发现,前面放大的倍数虽然不同,但是我们的 viewBox前两个参数没变
,内部 rect 元素的设置的宽高也没变
,但是都是很巧合地均有 1/4
渲染在了 svg 元素中,所以这里的viewBox前两个参数效果有两种可能
,要么是具体的像素值,然后在缩放之前进行移动
,要么就是存在某种比例关系,在缩放之后移动
。下面我们继续进行分析:
viewBox="100 100 300 300"
<svg width="300px" height="300px" viewBox="20 20 100 100" style="border: 1px solid red;"> <rect x="0" y="0" width="100" height="100" fill="green"></rect> </svg>
通过这个,如果 上述第一种假设(前两个参数是具体值,先移动,再放大)的话,移动完之后的中心点是(30,30),放大之后的被隐藏掉的部分的宽就应该是 100 * 3 - 30 = 270,但是现在是60,所以这种假设不成立。
现在来验证第二种假设,先放大,那么放大之后的中心点的坐标为(150,150),再进行移动,移动的距离为 20 /100 * 3 = 60,符合事实。那么细心的朋友就会问了,为什么之前的数据刚好对的上呢,是因为 50 这个数字,既可以在第一种假设中充当50px,又可以在后面的缩放中充当 50% 这个比例, 50% * 100 =50,这是一个巧合。
所以我们现在弄清楚viewBox四个参数的作用:后面两个与缩放的倍数有关,前面两个与缩放之后移动的比例有关。
回想——如果宽高的放大的倍数不一致呢
现在我们弄清楚了这四个参数的作用。当时我们留下一个问题,就是缩放时,宽高比不一致该如何呢?
- 设置
viewBox="0 0 50 100"
渲染的dom的宽高是初始宽高的3倍
,这里svg的width / viewBox的width = 6
,svg的height/ viewBox的height = 3
,所以我们猜测是按照较小的倍数进行缩放。下面进行验证: - 设置
viewBox="0 0 150 100"
上述比值较小的为2
,这个也放大到了原来的2
倍。那我们要是缩小呢? - 设置
viewBox="0 0 600 1200"
这里比值较小的为1/4
,渲染出来的dom也是原来的1/4
,由此可以证实结论:如果对应的宽高不一致的话,那么按照比值较小的那个比例进行缩放。
(不过,细心的朋友们会发现,这里并没有在左上角对齐,这里就与<svg>的另一个属性preserverAspectRatio
有关系了,这里我们先不讨论)
结案
我们终于将“案情”查了个水落石出,下面就来对“案子”进行总结:
假设有如下svg元素:
<svg width="width-1" height="height-1" viewBox="min-x min-y width-2 height-2">
/* 内部元素…… */
<rect width="width-3" height="height-3" fill="green"></rect>
</svg>
那么,内部元素就会先按照 width-1 / width-2
与 height-1 / height-2
中较小的那个比例进行缩放(假设这个较小的比例为 m
),然后在缩放的基础上,水平方向上移动 min-x / width-3 * m
个像素,正值向左移动,负值向右移动,竖直方向上移动 min-y / height-3 * m
个像素,正值向上移动,负值向下移动(这两个方向与通常情况下
的坐标系的正方向相反
)。最后感谢大家能够看到这里,不妨留下一个三连吧✨!