坐标系
撰写时间:2024-07-18
修订时间:2024-07-19
概述
SVG的坐标系有其较为独特的特点,涉及到视口、用户坐标系、视口坐标系、viewBox等术语,我们需明确这些术语的语义及其作用,才能用好SVG这个强大的绘图工具。
本章中,我们将一一剖析这些术语的由来,及其确切的含义。
最简单的坐标系
坐标系的目的在于确定元素的位置,以及大小、范围。
SVG的默认坐标系,原点在左上角,X正轴朝右,Y正轴朝下。我们称此坐标系为用户坐标系。
通过width及height属性值确定了svg的大小,由于未指定单位,默认为px
。
视口概念的引入
默认视口
下面在X轴为250px的位置开始绘制矩形。
超出svg大小范围之外的部分未予以绘制。
理论上,坐标系的X轴与Y轴可无限延伸,没有终值。但在计算机中,屏幕是有物理尺寸大小限制的,我们只能在一定范围内绘制特定的图形。
因此,SVG引入了视口(viewport)的概念,在坐标系的基础上,加入了宽、高值的因子,以确定SVG图像绘制的可见区域。
视口区域为一矩形。默认情况下,左上角位于用户坐标系的原点,其宽高值均取自svg元素的width及height属性值。超出视口区域部分的图像不予绘制,我们称之为被裁剪掉了。
自定义视口
在最外层的svg中,嵌套使用另一个svg即可定义一新视口。
默认情况下,图像在整个svg的物理大小范围内进行绘制。也即,默认视口的大小即整个svg的大小。但上面,我们通过使用另一个嵌套的svg元素,在画布内居中定义了一个新的视口,则嵌套的svg所包含的所有子元素,将在此视口内进行绘制。
嵌套svg的各个属性值均使用了百分比的形式,这样做的好处是,当我们改变了最外层svg的宽高属性值,新视口总能居中定位。
嵌套svg的作用,不仅在画布的相应位置重新定义了一个新的视口,它还隐式地创建了一个新的坐标系。它所包含的所有子元素,上面仅为一个rect元素,即在此新的坐标系中进行绘制。因此,其所包含的任何元素所绘制出来的图像,均不会超出其所定义的视口范围。
除了svg元素之外,symbol元素也可以定义视口。
从多视口看视口坐标系
一个视口划定了一个图像绘制的区域,而一个svg元素可以含有多个视口。通过使用嵌套的svg可以建立起一个新的视口。
上面,使用两个嵌套的svg,并在相关属性上使用百分比的形式来设置值,从而在最外层的svg的左上角区域及右下角区域,分别新建立了两个视口,用于绘制不同的图形。
可以看出,两个视口分别建立起了两个独立的坐标系,视口下的子元素使用其所属视口的坐标系。
这两个坐标系不同于唯一的用户坐标系,可以有各自不同的原点、坐标轴朝向及大小。我们称之为视口坐标系(viewport coordinate)。
因此,SVG视口有两套坐标系,一套为用户坐标系,一套为视口坐标系。
SVG元素 | 用户坐标系 | 视口坐标系 | ||
---|---|---|---|---|
原点 | 大小 | 原点 | 大小 | |
最外层svg | (0, 0) | 200px × 200px | (0, 0) | 200px × 200px |
第一个子svg | (0, 0) | 100px × 100px | (0, 0) | 100px × 100px |
第二个子svg | (100, 100) | 100px × 100px | (0, 0) | 100px × 100px |
每个SVG子元素均使用父元素的视口坐标系。下面我们使用数值而非百分比的方式来重新体绘制两个视口。
最外层的svg的视口坐标系,其原点位置位于(0, 0),大小为200px × 200px。
作为两个子svg,通过下面的语句定义了两个新视口:
这两个子svg的x及y属性的值,均来自于它们的父元素svg的视口坐标系中的值。
而两个子svg下的元素,又各自使用了它们父级svg的视口坐标系:
因此,尽管它们的代码完全一样,却使用了两个不同的视口坐标系,从而在不同的地方绘制出同样的图像。
这里可以看出,在使用SVG绘制图形时,用户应使用视口坐标系来定位各个元素,而SVG在后台自动将视口坐标系转换为用户坐标系,从而在画布上绘制出图形。
一般情况下,我们较少用到多个视口。但SVG坐标系的概念是建立在视口的基础上的,即使只使用一个默认的视口,所使用的坐标系也是视口坐标系。因此,理解视口以及视口坐标系的概念很重要。
viewBox属性
问题的提出
现在来看一个具体用例。下面是第三方绘制的一个图形:
其svg的大小为200px * 120px,然后,使用circle及path在上面绘制了一个圆内嵌等腰三角形。
现在,根据Boss的指令,我们要使用这个图形,且需将其放在100px × 200px的画布上。我们先设定好svg的大小,然后直接复制粘贴其子元素的代码,效果如下:
由于源svg的大小与目标svg的大小不一致,导致迁移后图形被裁剪掉了,且布局排列也不好看。
很明显,我们需要更改其源代码。但由于其使用了path元素,我们需要对其数据进行重新调整,工作量很大,且不一定完全吻合原来的图形。我们也可以通过平移、缩放等变换达到目的,但也得需要经过仔细调节。
无论采取哪种方式,其本质,就是要将原来的坐标系为200px × 120px的图形转换为现在的100px × 200px的坐标系中。并且,最好能保持原图形的宽高比率,如果可能,还能自动在水平垂直方向上设定排列方式。
viewBox的作用
viewBox属性应运而生。它可以将特定的用户坐标系映射至视口坐标系中。对于上面的代码,我们只需在svg元素中为其设置viewBox属性值就行了,其他代码无需更改。
只需将viewBox的值设定为原来的坐标系0 0 200 120,SVG就会自动将此坐标系按比率映射至0 0 100 200的新坐标系中。
源与目标的宽高比如下表:
宽 | 高 | 宽高比 | 轴向 | |
---|---|---|---|---|
源 | 200 | 120 | 200 ÷ 120 ≈ 1.67 | 横屏 |
目标 | 100 | 200 | 100 ÷ 200 = 0.5 | 竖屏 |
源为横屏,目标为竖屏,上面的效果就是将一个横屏的图像按比例缩放至一个竖屏上,并在竖屏上居中排列。
可见,viewBox属性的主要目的,在于将原来已在特定坐标系中绘制的图形,按照一定的条件,映射至新的视口坐标系中。并且,由于源与目标的宽高比不一致,SVG会在相应的轴向上居中排列图形。
需注意的是,设定了viewBox属性值后,其各个子元素所使用的坐标系应为viewBox所指定的源坐标系,这个坐标系,我们称之为用户坐标系(user coordinate system)。SVG会自动帮我们转换为目标坐标系,也即视口坐标系。
preserveAspectRatio
设置了viewBox后,我们还可以通过preserveAspectRatio属性进行进一步的精准控制。正如其属性名称,其作用为如何保持宽高比
。
none
横排:
竖排:
当preserveAspectRatio的值为none时,图形也会缩放,但其缩放是不按原宽高比率来缩放
,也即,将源坐标系的图形简单地映射至目标坐标系。其过程是,将源区域的4个角,分别拉至目标区域对应的4个角,或称,简单而粗暴的边角定位。而此时如果源与目标的宽高比率不一致,就会产生变形的效果。
如果我们需要按比率进行缩放,则由于目标区域与源区域可能不一致,就会产生两个问题。第一个问题:对于按比率缩放后的图形,如果目标区域无法完全容纳,是再进行进一步的缩放以显示全部,还是将超出区域外的部分裁剪掉?(meet or slice?)
第二个问题:如何在水平、垂直轴向上排列经重新缩放后或保留下来的图形?(alignment?)
因此,我们需要将preserveAspectRatio属性按特定的格式来设置其值,以回答上述这两个问题。
meet
meet表示按比率缩放,且保证在目标坐标系区域中绘制源坐标系中的全部图形。
横排
由于目标坐标系比较宽,因此先将源图形的高与目标区域的高相匹配,再按源的宽高比求出实际宽度。此时在水平方向上会产生多余的空间,则可使用xMin, xMid或xMax的值来分别进行水平方向上的左对齐、居中对齐或右对齐。
在上面代码中切换修改不同的参数值,查看运行效果。
尽管此时YMin, YMid或YMax无论取何值都一样,但也必须同时提供。
竖排
由于目标坐标系比较高,因此先将源图形的宽与目标区域的宽相匹配,再按源的宽高比求出实际高度。此时在垂直方向上会产生多余的空间,则可使用YMin, YMid或YMax的值来分别进行垂直方向上的上对齐、居中对齐或下对齐。
在上面代码中切换修改不同的参数值,查看运行效果。
尽管此时xMin, xMid或xMax无论取何值都一样,但也必须同时提供。
slice
slice表示按比率缩放,但超出目标坐标系区域外的图形将被裁剪。
slice的缩放算法与meet的缩放算法不一样,其具体算法步骤如下:
第一步,先分别比较源与目标在宽、高上的缩放比率。
属性 | 源 | 目标 | 轴向缩放比率 |
---|---|---|---|
width | 200 | 300 | 300 ÷ 200 = 1.5 |
height | 120 | 240 | 240 ÷ 120 = 2 |
第二步,取两个比率的最大值作为缩放因子,分别在源的宽、高上进行缩放。
属性 | 源 | 缩放因子 | 缩放后的值 |
---|---|---|---|
width | 200 | 2 | 200 × 2 = 400 |
height | 120 | 2 | 120 × 2 = 240 |
第三步,将缩放后大小为400px × 240px的图像放在目标为300px × 240px的区域内绘制,超出部分将被裁剪掉。
上图中,红色边框区域为由viewBox所指定的源区域,从图中可以看出,因目标区域宽度不够,源图形的右边被裁剪掉了。
修改svg的width值,变动值域为[300px, 400px],可以看到,随着宽度的增加,源图形被裁剪掉的右边渐渐显示出来。当其值为400px时,将可看到全部的红色边框。这是因为此时两个轴向上宽高比均为2,因此目标区域正好有空间完全容纳下经缩放后的图形。
将代码中的xMin
分别改为xMid
或xMax
,可清晰地看到红色边框移动的情况。因此,红色边框可帮我们快速认识到哪部分被保留下来。
任意修改svg的width及height属性值,观察被保留下来的图像部分。例如,500px × 150px,等等。不管这两个值为多少,使用xMidYMid slice
都会居中保留相应的部分。
symbol
基本图形
我们在一个大小为300px × 300px的svg上绘制出一张科技元素的图像。svg的外框已通过CSS用白色标出。
注意,这个svg只有width及height属性,没有viewBox属性。并且,我们是在整个画布上绘制了图像。