理解SVG坐标系和变换(第一部分)-viewport,viewBox,和preserveAspectRatio
(本文转自w3cplus,这里仅修正了部分个人认为翻译不恰当之处;下面是译文链接,英文原文链接在结尾处提供)
- 理解SVG坐标系和变换(第一部分)-viewport,viewBox,和preserveAspectRatio
- 理解SVG坐标系和变换(第二部分)-transform属性
- 理解SVG坐标系和变换(第三部分)-建立新视口
SVG元素不像HTML元素一样由CSS盒模型管理。这使得我们可以更加灵活定位和变换这些元素-也许一眼看上去不太直观。然而,一旦你理解了SVG坐标系和变换,操纵SVG会非常简单并且很有意义。本篇文章中我们将讨论控制SVG坐标系的最重要的三个属性:viewport, viewBox, 和 preserveAspectRatio。
这是本系列三篇文章中的第一篇,这篇文章讨论SVG中的坐标系和变换。
为了使文中的内容和解释更形象化,我创建了一个互动演示,你可以任意改变viewBox 和 preserveAspectRatio的值。
这个例子只是主要内容的一小部分,所以看完请回来继续阅读这篇文章
SVG画布
canvas是绘制SVG内容的一块空间或区域。理论上,画布在所有维度上都是无限的。所以SVG可以是任意尺寸。然而,SVG通过有限区域展现在屏幕上,这个区域叫做viewport。SVG中超出视口边界的区域会被裁切并且隐藏。
viewport视口
视口是一块SVG可见的区域。你可以把视口当做一个窗户,透过这个窗户可以看到特定的景象,景象也许完整,也许只有一部分。
SVG的视口类似访问当前页面的浏览器视口。网页可以是任何尺寸;它可以大于视口宽度,并且在大多数情况下都比视口高度要高。然而,每个时刻只有一部分网页内容是透过视口可见的。
整个SVG画布可见还是部分可见取决于这个canvas的尺寸以及preserveAspectRatio属性值。你现在不需要担心这些;我们之后会讨论更多的细节。
你可以在最外层元素上使用width和height属性声明视口尺寸。
<!-- the viewport will be 800px by 600px -->
<svg width="800" height="600">
<!-- SVG content drawn onto the SVG canvas -->
</svg>
在SVG中,值可以带单位也不可以不带。一个不带单位的值可以在用户空间中通过用户单位声明。如果值通过用户单位声明,那么这个值的数值被认为和px单位的数值一样。这意味着上述例子将被渲染为800px*600px的视口。
你也可以使用单位来声明值。SVG支持的长度单位有:em,ex,px,pt,pc,cm,mm,in和百分比。
一旦你设定最外层SVG元素的宽高,浏览器会建立初始视口坐标系和初始用户坐标系。
The initial coordinate system初始坐标系
The initial viewport coordinate system初始视口坐标系是一个建立在视口上的坐标系。原点(0,0)在视口的左上角,X轴正向指向右,Y轴正向指向下,初始坐标系中的一个单位等于视口中的一个"像素"。这个坐标系统类似于通过CSS盒模型在HTML元素上建立的坐标系。
The initial user coordinate system初始用户坐标系是建立在SVG画布上的坐标系。这个坐标系一开始和视口坐标系完全一样-它自己的原点位于视口左上角,x轴正向指向右,y轴正向指向下。使用viewBox属性,初始用户坐标系统-也称当前坐标系,或使用中的用户空间——可以被修改成与视口坐标系不一样的坐标系。我们在一下节中讨论如何改变坐标系。
到现在为止,我们还没有声明viewBox属性值。SVG画布的用户坐标系统和视口坐标系统完全一样。
下 图中,视口坐标系的"标尺"是灰色的,用户坐标系(viewBox)的是蓝色的。由于它们在这个时候完全相同,所以两个坐标系统重合了。
上面SVG中的鹦鹉的外框边界是200个单位(这个例子中是200个像素)宽和300个单位高。鹦鹉基于初始坐标系在画布中绘制。
新用户空间(即,新当前坐标系)也可以通过在容器元素或图形元素上使用transform属性来声明变换。我们将在这篇文章的第二部分讨论关于变换的内容,更多细节在第三部分和最后部分中讨论。
viewBox
我喜欢把viewBox理解为“真实”坐标系。毕竟,它是用来把SVG图形绘制到画布上的坐标系。这个坐标系可以大于视口也可以小于视口,在视口中可以整体可见或部分可见。
在本小节之前,这个坐标系-用户坐标系-和视口坐标系完全一样。因为我们没有把它声明成其他坐标系。这就是为什么所有的定位和绘制看起来是基于视口坐标系的。因为我们一旦创建视口坐标系(使用width和height),浏览器默认创建一个完全相同的用户坐标系。
你可以使用viewBox属性声明自己的用户坐标系。如果你选择的用户坐标系统和视口坐标系统宽高比(高比宽)相同,它会延伸来适应整个视口区域(我们一会儿会讲一些例子)。然而,如果你的用户坐标系宽高比不同,你可以用preserveAspectRatio属性来声明整个系统在视口内是否可见,你也可以用它来声明在视口中如何定位。我们会在下个章节里讨论这一情况的细节和例子。在这一章里,我们只讨论viewBox的宽高比符合视口的情况-在这些例子中,preserveAspectRatio不产生影响。
在我们讨论这些例子前,我们回顾一下viewBox的语法。
viewBox语法
viewBox属性接收四个参数值,包括:min-x,min-y , width 和 height。
viewBox = <min-x><min-y><width><height>
min-x和min-y值决定viewBox的左上角,width和height决定viewBox的宽高。这里要注意视口的宽高不需要和父元素<svg>
的宽高一样。负数对于width和height是无效的。值为0的话会禁止元素渲染。
注意viewport的宽度也可以在CSS中设置为任何值。例如:设置width:100%会让SVG视口在文档中自适应。无论viewBox的值是多少,它会映射为外层SVG元素计算出的宽度值。
设置viewBox的例子如下:
<!-- The viewbox in this example is equal to the viewport, but it can be different -->
<svg width="800" height="600" viewbox="0 0 800 600">
<!-- SVG content drawn onto the SVG canvas -->
</svg>
如果你之前在其他地方看到过viewBox,你也许会看到一些解释说你可以用viewBox属性通过缩放或者变化使SVG图形变换。这是真的。我将深入探究并且告诉你甚至可以使用viewBox来切割SVG图形。
理解viewBox和viewport之间差异最好的方法是亲身观察。所以让我们看一些例子。我们将从viewBox和viewport的宽高比相同的例子开始,所以我们还不需要深入了解preserveAspectRatio。
①与viewport宽高比一致的viewBox
我们从一个简单的例子开始。这个例子中的viewBox的尺寸是viewport的尺寸的一半。在这个例子中我们不改变viewBox的原点,所以min-x和min=y都设置成0。viewBox的宽高是viewport宽高的一半。这意味着我们保持宽高比。
<svg width="800" height="600" viewbox="0 0 400 300">
<!-- SVG content drawn onto the SVG canvas -->
</svg>
所以,viewBox="0 0 400 300"到底有什么用呢?
- 它声明了一个特定的区域,canvas横跨左上角的点(0,0)到点(400,300)。
- SVG图像被这个区域裁切。
- 区域被拉伸(类似缩放效果)来充满整个视口。
- 用户坐标系被映射到视口坐标系-在这种情况下-一个用户单位等于两个视口单位。
下面的图片展示了在我们例子中把上面的viewBox应用到 画布中的效果。灰色单位代表视口坐标系,蓝色坐标系代表viewBox建立的用户坐标系。
任何在SVG画布中画的内容都会被对应到新的用户坐标系中。
我喜欢像Google地图一样通过viewBox把SVG画布形象化。在Google地图中你可以在特定区域缩放;这个区域是唯一可见的,并且在浏览器视口中按比例增加。然而,你知道地图的剩余部分还在那里,但是不可见因为它超出视口的边界-被裁切了。
现在让我们试着改变min-x和min-y的值。都设置为100。你可以设置成任何你想要的值。宽高比还是和视口的宽高比一样。
<svg width="800" height="600" viewbox="100 100 200 150">
<!-- SVG content drawn onto the SVG canvas -->
</svg>
添加viewBox="100 100 200 150"的效果和之前例子中一样都是裁切的效果。图形被裁切然后拉伸来充满整个视口区域。
同样,用户坐标系统被映射到视口坐标系统——200个用户单元被映射到800个视口单元,这样每个用户单元就等于4个视口单元。这就产生了一个缩放(放大)效果,就像你在上面的截图中看到的那样。
另外注意,在这个时候,为和声明非0的值对图形有变换的效果;更加特别的是,SVG 画布看起来向上拉伸100个单位,向左拉伸100个单位(transform="translate(-100 ,-100)"
)。
的确,作为规范说明,“viewBox属性的影响在于用户代理自动添加适当的变换矩阵来把用户空间中具体的矩形映射到指定区域的边界(通常是视口)”。
这只是我们之前提到过的一种说法:图形被裁切然后被缩放以适应视口。这个说明随后增加了一个注释:“在一些情况下用户代理在缩放变换之外需要增加一个移动变换。例如,在最外层的svg元素上,如果viewBox属性对min-x和min-y声明非0值得那么就需要移动变换。”
为了更好演示移动变换,让我们试着给min-x和min-y添加-100。移动效果类似transform="translate(100, 100)";这意味着图形会在切割和缩放后移动到右下方。回顾倒数第二个裁切尺寸为400*300的例子,添加新的无效min-x和min-y值,新的代码如下:
<svg width="800" height="600" viewbox="-100 -100 400 300">
<!-- SVG content drawn onto the SVG canvas -->
</svg>
给图形添加上述viewBox transformation的结果如下图所示:
注意,与transform属性不同,因为viewBox自动添加的tranfomation不会影响有vewBox属性的元素的x, y, width和height等属性。因此,在上述例子中展示的带有width,height和viewBox属性的svg元素,width和height属性代表添加viewBox 变换之前的坐标系中的值。在上述例子中你可以看到初始(灰色)viewport坐标系甚至在<svg>上使用了viewBox属性后仍然没有影响。
另一方面,像tranform属性一样,它给所有其他属性和后代元素建立了一个新的坐标系。你还可以看到在上述例子中,用户坐标系是新建立的-它不是保持像初始用户坐标系和使用viewBox前的视口坐标系一样。任何后代会在这个新的用户坐标系中定位和确定尺寸,而不是初始坐标系。
最后一个viewBox的例子和前一个类似,但是它不是切割画布,我们将在viewport里扩展它并看它如何影响图形。我们将声明一个宽高比viewport大的viewBox,并依然保持viewport的宽高比。我们在下一章里讨论不同的宽高比。
在这个例子中,我们将viewBox的尺寸设为viewport的1.5倍。
<svg width="800" height="600" viewbox="0 0 1200 900">
<!-- SVG content drawn onto the SVG canvas -->
</svg>
现在用户坐标系会被放大到1200*900。它会被映射到视口坐标系,用户坐标系中的每一个单位水平方向上等于视口坐标系中的viewport-width / viewBox-width,竖直方向上等于viewport-height / viewBox-height。这意味着,在这种情况下,每一个用户坐标系中的x-units等于viewport坐标系中的0.66个x-units,每个用户y-unit映射成0.66的viewport y-units。
当然,理解这些最好的方法是把结果视觉化。viewBox被缩放到适应下图所示的viewport。因为图形在画布里基于新的用户坐标系绘制的,而不是视口坐标系,它看起来比视口小。
②与viewport宽高比不一致的viewBox
到目前为止,我们所有的例子的宽高比都和视口一致。但是如果viewBox中声明的宽高比和视口中的不一样会发生什么呢?例如,试想我们把视口的尺寸设为1000*500。宽高比不再和视口的一样。在例子中使用viewBox="0 0 1000 500"的结果如下图:
用户坐标系。因此图形在视口中定位:
- 整个viewBox适应viewport。
- 保持viewBox的宽高比。viewBox没有被拉伸来覆盖视口区域。
- viewBox在视口中水平垂直居中。
这是默认表现。那用什么控制表现呢?如果我们想改变视口中viewBox的位置呢?这就需要用到preserveAspectRatio属性了。
preserveAspectRatio属性
preserveAspectRatio属性强制统一缩放比来保持图形的宽高比。
如果你用不同于视口的宽高比定义用户坐标系,如果像我们在之前的例子中看到的那样浏览器拉伸viewBox来适应视口,宽高比的不同会导致图形在某些方向上扭曲。所以如果上一个例子中的viewBox被拉伸以在所有方向上适应视口,图形看起来如下:
当给viewBox设置0 0 200 300的值时扭曲显而易见(显然这很不理想),这个值小于视口尺寸。我故意选择这个尺寸从而让viewBox匹配鹦鹉边界盒子的尺寸。如果浏览器拉伸图像来适应整个视口,看起来会像下面这样:
preserveAspectRatio属性让你可以在保持宽高比的情况下强制统一viewBox的缩放比,并且如果不想用默认居中你可以声明viewBox在视口中的位置。
preserveAspectRatio语法
preserveAspectRatio的官方语法是:
preserveAspectRatio = defer? <align> <meetOrSlice>?
它在任何建立新viewport的元素上都有效(我们会在这个系列的下一部分讨论这个问题)。
①defer声明是可选的,并且只有当你在<img>上添加preserveAspectRatio才被用到。用在任何其他元素上时它都会被忽略。本身不在这篇文章的讨论范围,我们暂时跳过defer这个选项。
-
②align参数声明是否强制统一放缩,如果是,对齐方法会在viewBox的宽高比不符合viewport的宽高比的情况下生效。
如果align值设为none,例如:
preserveAspectRatio = "none"
图形不在保持宽高比而会缩放来适应视口,像我们在上面两个例子中看到的那样。
其他所有preserveAspectRatio值都在保持viewBox的宽高比的情况下强制拉伸,并且指定在视口内如何对齐viewBox。我们会简短介绍align的值。
-
③最后一个属性,meetOrSlice也是可选的,默认值为meet。这个属性声明整个viewBox在视口中是否可见。如果是,它和align参数通过一个或多个空格分隔。例如:
preserveAspectRatio = "xMinYMin slice"
这些值第一眼看起来也许很陌生。为了让它们更易于理解和熟悉,你可以把meetOrSlice的值类比于background-size的contain和cover值;它们非常类似。meet类似于contain,slice类似于cover。下面是每个值的定义和含义:
- meet(默认值)
基于以下两条准侧尽可能缩放元素:
- 保持宽高比
- 整个viewBox在视口中可见
在这个情况下,如果图形的宽高比不符合视口,一些视口会超出viewBox的边界(即viewBox绘制的区域会小于视口)。(在viewBox一节查看最后的例子。)在这个情况下,viewBox的边界被包含在viewport中使得边界满足。
这个值类似于background-size: contain。背景图片在保持宽高比的情况下尽可能缩放并确保它适合背景绘制区域。如果背景的长宽比和应用的元素的长宽比不一样,部分背景绘制区域会没有背景图片覆盖。
- slice
在保持宽高比的情况下,缩放图形直到viewBox覆盖了整个视口区域。viewBox被缩放到正好覆盖视口区域(在两个维度上),但是它不会缩放任何超出这个范围的部分。换而言之,它缩放到viewBox的宽高可以正好完全覆盖视口。
在这种情况下,如果viewBox的宽高比不适合视口,一部分viewBox会扩展超过视口边界(即,viewBox绘制的区域会比视口大)。这会导致部分viewBox被切片。
你可以把这个类比为background-size: cover。在背景图片的情况中,图片在保持本身宽高比(如何)的情况下缩放到宽高可以完全覆盖背景定位区域的最小尺寸。
所以,meetOrSlice被用来声明viewBox是否会被完全包含在视口中,或者它是否应该尽可能缩放来覆盖整个视口,甚至意味着部分的viewBox会被“slice”。
例如,如果我们声明viewBox的尺寸为200*300,并且使用了meet和slice值,保持align值为浏览器默认,每个值的结果会看起来如下:
align参数使用9个值中的一个或者为none。任何除none之外的值都用来保持宽高比缩放图片,并且还用来在视口中对齐viewBox。
当使用百分比值时,align值类似于background-position。你可以把viewBox当做背景图像。通过align定位和background-position的不同在于,它不是将viewbox的特定点定位在viewport的相应点上,而是将viewbox的特定“轴”与viewport的相应“轴”对齐。
为了理解每个align值的含义,我们将首先介绍每一个“轴”。
还记得viewBox的min-x和min-y值吗?我们将使用它们来定义viewBox中的"min-x"和"min-y"轴。另外,我们将定义两个轴“max-x”和”max-y“,各自通过 <min-x>+<width> 和 <min-y>+<height> 来定位。最后,我们定义两个轴"mid-x"和"mid-y",根据<min-x> + (<width>/2) 和 <min-y>+ (<height>/2)来定位。
这样做是不是让事情更复杂了呢?如果是这样,让我们看一下下面的图片来看一下每个轴代表了什么。在这张图片中,min-x和min-y 值都设置为0。viewBox被设置为viewBox = "0 0 300 300"。
上面图片中的灰色虚线代表视口的mid-x和mid-y轴。我们将对它们赋一些值来对齐viewBox的mid-x和mid-y轴。对于视口,min-x的值等于0,min-y值也等于0,max-x值等于viewBox的宽度,max-y的值等于高度,mid-x和mid-y代表了宽度和高度的中间值。
对齐的取值包括:
①none
不强制统一缩放。如果必要的话,在不统一(即不保持宽高比)的情况下缩放给定元素的图像内容直到元素的边界盒完全匹配是视口矩形。
换句话说,如果有必要的话viewBox被拉伸或缩放来完全适应整个视口,不管宽高比。图形也许会扭曲。
(注意:如果的值是none,可选的值<meetOrSlice>无效。)
②xMinYMin
- 强制统一缩放
- 视口X轴的最小值对齐元素viewBox的<min-x>。
- 视口Y轴的最小值对齐元素viewBox的<min-y>。
- 把这个类比为backrgound-position: 0% 0%;。
③xMinYMid
- 强制统一缩放。
- 视口X轴的最小值对齐元素viewBox的<min-x>。
- 视口Y轴的中间值来对齐元素的viewBox的中间值。
- 把这个类比为backrgound-position: 0% 50%;。
④xMinYMax
- 强制统一缩放。
- 视口X轴的最小值对齐元素viewBox的<min-x>。
- 视口X轴的最大值对齐元素的viewBox的<height>+<min-y>。
- 把这个类比为backrgound-position: 0% 100%;。
⑤xMidYMin
- 强制统一缩放。
- 视口X轴的中间值对齐元素的viewBox的X轴中间值。
- 视口Y轴的最小值对齐元素的viewBox的最小值 。
- 把这个类比为backrgound-position: 50% 0%;。
⑥xMidYMid (默认值)
- 强制统一缩放。
- 视口X轴的中间值对齐元素的viewBox的X轴中间值。
- 视口Y轴的中间值对齐元素的viewBox的Y轴中间值。
- 把这个类比为backrgound-position: 50% 50%;。
⑦xMidYMax
- 强制统一缩放。
- 视口X轴的中间值对齐元素的viewBox的X轴中间值。
- 视口Y轴的最大值对齐元素的viewBox的<height>+<min-y>。
- 把这个类比为backrgound-position: 50% 100%;。
⑧xMaxYMin
- 强制统一缩放。
- 视口X轴的最大值对齐元素的viewBox的 <width>+<min-x>。
- 视口Y轴的最小值对齐元素的viewBox的<min-y>。
- 把这个类比为backrgound-position: 100% 0%;。
⑨xMaxYMid
- 强制统一缩放。
- 视口X轴的最大值对齐元素的viewBox的 <width>+<min-x>。
- 视口Y轴的中间值对齐元素的viewBox的Y轴中间值。
- 把这个类比为backrgound-position: 100% 50%;。
⑩xMaxYMax
- 强制统一缩放。
- 视口X轴的最大值对齐元素的viewBox的 <width>+<min-x>。
- 视口Y轴的最大值对齐元素的viewBox的 <height>+<min-y>。
- 把这个类比为backrgound-position: 100% 100%;。
所以,通过使用preserveAspectRatio属性的align和meetOrSlice值,你可以声明是否统一缩放viewBox,是否和视口对齐,在视口中是否整个可见。
有时候,取决于viewBox的尺寸,一些值可能会导致相似的结果,例如在早先viewBox="0 0 200 300"的例子中,一些对齐完全用了不同的align值。这时候就要设置meetOrSlice的值为meet来保证viewBox包含在viewport内。
如果我们把meetOrSlice的值改成slice,不同的值我们将得到不同的结果。注意viewBox是如何拉伸来覆盖整个视口的。x轴被拉伸到用200单位来覆盖视口800单位。为了达到这个目的,并且保持viewBox的宽高比,y轴在底部被“裁切”,但是你可以想象它在视口中高度上的延伸。
当然,不同的viewBox值看起来不同于我们这里用的200*300。为了保持简洁,我们不再列举更多的例子,你可以看我创建的一些互动演示来帮助你更好地形象化理解viewBox和preserveAspectRatio在不同值下的效果。你可以在一下节中查看互动演示例子的链接。
但是在这之前,我想要提醒你注意如果 <min-x>和<min-y> 值改变,那么mid-x, mid-y, max-x, 和 max-y的值也会发生改变。你可以在互动演示中改变这些值来查看轴以及相关联的viewBox的对齐方式的改变。
下面图片展示了定位轴的位置为viewBox = "100 0 200 300"时的效果。和之前用一样的例子,但是我们把的<min-x>值设为100而不是之前的0。你可以设置成任何你想要的值。注意min-x, mid-x, 和 max-x轴是如何变化的。这里使用的preserveAspectRatio值为默认的xMinYMin meet,意味着mid-*轴和视口轴的中间对齐。
互动演示
要理解viewport, viewBox, 以及不同的preserveAspectRatio值是如何工作的最好方法是可视化的演示。
出于这个目的,我创建了一个简单的互动演示,你可以改变这些属性的值来查看新值导致的结果。
我希望这篇文章在帮助你理解SVG viewport, viewBox, 和 preserveAspectRatio 内容时有作用。如果你想要了解更多关于SVG坐标系的内容,例如嵌套坐标系,建立一个新的坐标系以及SVG中的变换,继续阅读这一系列接下来的部分。感谢你的阅读!
本文根据SaraSoueidan的《Understanding SVG Coordinate Systems and Transformations (Part 1) — The viewport, viewBox, and preserveAspectRatio》一文所译,整个译文带有我们自己的理解与思想,如果译得不好或有不对之处还请同行朋友指点。如需转载此译文,需注明英文出处http://sarasoueidan.com/blog/svg-coordinate-systems/。