Chapter 6. Transforming The Coordinate System
到目前为止,所有图形都准确地绘制在它们属性所定义的地方。有时候,你想将一个图形进行旋转,缩放或移动到一个新的位置。为了完成这些任务,你将
transform 属性添加到到适当的 SVG 元素中。本章研究这些转换的细节。
The translate Transformation 平移转换
在第 5 章中,你见识了你可以使用 <use>
元素的 x 和 y 属性将一组图形对象(a group of graphic objects)放置到一个特定的地方。看一下样例6-1
中的 SVG,它定义了一个矩形,并绘制在了网格的左上角,然后重新绘制在左上角的坐标点(50, 50)处。图6-1中的虚线并不是SVG的一部分,而是用来展示我们感
兴趣的部分。
Example 6-1.Moving a graphic with use
<svg width="200px" height="200px" viewBox="0 0 200 200"
xmlns="http://www.w3.org/2000/svg">
<g id="square">
<rect x="0" y="0" width="20" height="20"
style="fill: black; stroke-width: 2;"/>
</g>
<use xlink:href="#square" x="50" y="50"/>
</svg>
事实证明,x 和 y 的值实际上时更一般和更强大的 transform 属性的一种简写形式。具体地说,x 和 y 的值等价于 transform=“translate(x-value,y-value)” 类似的属性,
这里 translate 是 move 的一种术语。 x-value 和 y-value 以当前用户坐标系统进行度量。让我们使用 transform 来获得与在左上角(50,50)的点处创建(矩形)一样的效果。
样例6-2 显示对应的 SVG。
Example6-2 Moving the coordinate system with translation
<svg width="200px" height="200px" viewBox="0 0 200 200"
xmlns="http://www.w3.org/2000/svg">
<g id="square">
<rect x="0" y="0" width="20" height="20"
style="fill: none; stroke:black; stroke-width: 2;"/>
</g>
<use xlink:href="#square" transform="translate(50,50)"/>
</svg>
结果显示如图6-1所示。您可能认为这是将方块移动到网格上的不同位置来实现的,如图6-2所示,但您可能错了。
在幕后真正发生的是一个完全不同的故事。它没有移动矩形,而是旋转整个网格,并将其移动到画布上的一个新位置。就正方形而言,它的左上角仍然是(0,0)。如图6-3所示:
在线示例允许您尝试使用不同的坐标:
http://oreillymedia.github.io/svg-essentials-examples/ch06/translate.html
警告:转换(transformation)不会改变图形对象的网格坐标;相反,它改变了网格在画布上的位置。
乍一看,使用平移(translate)似乎是荒谬和低效的,就像把你的沙发从房子的外墙移到一个新的位置,把整个客厅,墙壁和所有东西都移动到一个新的位置。
事实上,如果平移(translate)是唯一可用的变换,移动整个坐标系将是浪费。但是,您很快就会看到其他的转换和一系列转换的组合,如果它们应用于整个坐标系,
它们在数学上和概念上都更方面。
The scale Transformation
通过缩放坐标系统,可以使一个对象看起来比它所定义的尺寸更大或更小。这种变换具体说明如下:
- transform=“scale(value)”
用给定的值乘以所有的 x轴坐标 和 y轴坐标
- transform=“scale(x-value, y-value)”
用给定的 x-value 乘以所有的 x轴坐标,用给定的 y-value 乘以所有的 y轴坐标。
样例6-3 是第一种缩放变换的例子,使两个轴的比例都翻倍。同样,图6-4种的虚线不在SVG中;它只是显示我们感兴趣的画布区域。注意,正方形的左上角是(10, 10)。
Example 6-3. Uniformly scaling a graphic
http://oreillymedia.github.io/svg-essentials-examples/ch06/scale.html
<svg width="200px" height="200px" viewBox="0 0 200 200"
xmlns="http://www.w3.org/2000/svg">
<g id="square">
<rect x="10" y="10" width="20" height="20"
style="fill: none; stroke: black;"/>
</g>
<use xlink:href="#square" transform="scale(2)"/>
</svg>
你可能会想,“等一下,我能够明白为什么矩形会变大了。但是我不明白,我没有要求平移,为什么正方形会在不同地方呢?”当您查看图6-5,看清实际发生了什么时,一切都变得清晰了。网格线并没有移动;坐标系中的原点(0,0)还在同样的位置,但是现在每个用户坐标是它之前的两倍。您可以从网格线中看到,在新的更大的网格中,矩形的左上角仍然是(0,0),因为对象从来没有移动。这也解释了为什么较大的正方形轮廓较厚。stroke-width 忍让是一个用户坐标系单位,但是这个单位现在变成之前两倍那么大,所以笔画变粗了。
警告:缩放变换(scaling transformation)不会改变图形对象的网格坐标或笔画宽度;相反,它改变了坐标系统(网格)相对于画布的大小。
通过使用第二种形式的缩放变换,可以给坐标系统的 x轴(x-axis) 和 y轴(y-axis) 指定一个不同的缩放比例(scale factor)。样例6-4 绘制了一个
x轴(x-axis)缩放了三倍,y轴(y-axis)缩放了一倍半的矩形。就像你在图6-6中看到的那样,笔画的宽度也非均匀地缩放了。
Example 6-4.Nonuniform scaling of a graphic
<svg width="200px" height="200px" viewBox="0 0 200 200"
xmlns="http://www.w3.org/2000/svg">
<g id="square">
<rect x="10" y="10" width="20" height="20"
style="fill: none; stroke: black;"/>
</g>
<use xlink:href="#square" transform="scale(3, 1.5)"/>
</svg>
到目前为止,实例仅将 transform 属性应用于 <use>
元素。您可以通过组应用变换,以达到变换这一系列元素的目的:
<g id="group1" transform="translate(3, 5)">
<line x1="10" y1="10" x1="30" y2="30"/>
<circle cx="20" cy="20" r="10"/>
</g>
你可能也需要将一个变换(transformation)应用到单个对象(single object)或基本图形(basic shape)中。例如,这里有一个矩形,他的坐标系统放大
了三倍:
<rect x="15" y="20" width="10" height="5" transform="scale(3)" style="fill: none; stroke: black;"/>
相当清楚的是,缩放矩形的宽度和高度应该是未缩放矩形的三倍。然而,你可能会怀疑在矩形缩放之前或之后是否对 x轴 和 y轴 坐标进行验证。答案是,SVG
在验证任何形状的坐标之前,先将变换(transformation)应用于坐标系统。 样例6-5 是缩放矩形的SVG,如图6-7所示,其中网格线是在无缩放坐标系中绘制的。
Example 6-5. Transforming a single graphic
<!-- grid guide lines in non-scaled coordinate system -->
<line x1="0" y1="0" x2="100" y2="0" style="stroke: black;"/>
<line x1="0" y1="0" x2="0" y2="100" style="stroke: black;"/>
<line x1="45" y1="0" x2="45" y2="100" style="stroke: gray;"/>
<line x1="0" y1="60" x2="100" y2="60" style="stroke: gray;"/>
<!-- rectangle to be transformed -->
<rect x="15" y="20" width="10" height="5" transform="scale(3)" style="fill: none; stroke: black;"/>
注意:对一个形状应用变换(transformation)的效果,与将一个形状囊括在一个变换组(transformed group)中的效果是一样的。在前面的例子中,缩放后的矩形相当于这个SVG:
<g transform="scale(3)">
<rect x="15" y="20" width="10" height="5"
style="fill: none; stroke: black;"/>
</g>
Sequences of Transformations
在一个图形对象上可能会做多个变换(transformation)。您只需要将变幻(可以用空格或逗号分隔,但不是必须的)放在 transform 属性的值中。这是一个
经历了两个变换的矩形,一个平移(translate)和一个缩放(scale)(坐标轴是用来表示矩形确实移动了)。
<!-- draw axes -->
<line x1="0" y1="0" x2="0" y2="100" style="stroke: gray;"/>
<line x1="0" y1="0" x2="100" y2="0" style="stroke: gray;"/>
<rect x="10" y="10" height="15" width="20" transform="translate(30, 20) scale(2)" style="fill: gray;"/>
这相当于下面的嵌套组序列,两者都会产生如图6-8所示的结果:
<g transform="translate(30, 20)">
<g transform="scale(2)">
<rect x="10" y="10" height="15" width="20"
style="fill: gray;"/>
</g>
</g>
图6-9显示了每个阶段的变换发生了什么。
注意:执行一系列变换的顺序会影响结果。一般来说,变换A后变换B, 不会得到与变换B后变换A相同的结果。
样例6-6 绘制了与前面示例相同的矩形,用灰色表示。然后再用黑色绘制它,不过是先缩放(scale)再平移(translate)。就像你再图6-10中看到的结果,这两个矩形再画布上的位置明显不同。
Example 6-6.Sequence of transformations - scale followed by translate
<!-- draw axes -->
<line x1="0" y1="0" x2="0" y2="100" style="stroke: gray;"/>
<line x1="0" y1="0" x2="100" y2="0" style="stroke: gray;"/>
<rect x="10" y="10" width="20" height="15" transform="translate(30, 20) scale(2)" style="fill: gray;"/>
<rect x="10" y="10" width="20" height="15" transform="scale(2) translate(30, 20)" style="fill: black;"/>
黑色矩形最终离原点更远的原因是首先应用了缩放,所以在x轴方向上平移了20个单位,在
y轴方向上平移了10个单位,而现在平移的单位是之前单位的两倍大。如图6-11所示。
网上的例子中,你可以实验任意序列的变换(transformations),并将变换后的矩形与原始矩形进行比较:
http://oreillymedia.github.io/svg-essentials-examples/ch06/sequence.html
Technique: Converting from Cartesian Coordinates 技巧:凑够笛卡尔坐标转换
如果您正在从其他系统向 SVG 传输数据,则可能需要处理使用笛卡尔坐标(高中代数中学习的坐标)表示数据的矢量图形。在笛卡尔坐标系中,(0, 0) 点在
画布的左下方,y 坐标随着向上移动而增加。图6-12显示了用直角坐标系画的梯形的坐标。
y轴相对于SVG默认是上下颠倒的,所以坐标需要重新计算。您可以使用一系列转换让SVG为你完成所有工作,而不是手工完成。首先,将图片平移到SVG中,坐标如样例6-7所示。(示例还包括作为引导的轴。)没人会感到惊讶的是,这张图会颠倒过来。注意,图6-13中的图像不是从左到右的反转,因为x轴在笛卡尔坐标系和默认的SVG坐标系中指向同一个方向。
Example 6-7. Direct use of Cartesian coordinates
<svg width="200px" height="200px" viewBox="0 0 200 200"
xmlns="http://www.w3.org/2000/svg">
<!-- axes -->
<line x1="0" y1="0" x2="100" y2="0" style="stroke: black;"/>
<line x1="0" y1="0" x2="0" y2="100" style="stroke: black;"/>
<!-- trapezoid -->
<polygon points="40 40, 100 40, 70 70, 40 70"
style="fill: gray; stroke: black;"/>
</svg>
要将图像从右侧向后翻转,你可以利用这样一个事实,即用负值缩放形状,将反转坐标顺序。但是,由于整个网格最终倍翻转到了零坐标的另一边,所以还需要将形状变换会画布的可见部分。转换遵循以下步骤:
- 求原图中的最大y轴坐标,在本例中,结果是100,在原来的y轴端点。
- 将整个图形包括在
<g>
元素中。 - 输入一个平移,将坐标系统向下移动最大值y: transform=“translate(0, max-y)”.
- 下一步变换将用 -1 作为因子对y轴进行缩放,使其上下翻转: transform=“translate(0, max-y) scale(1, -1)”。
备注: 你不想改变x轴的值,但仍需要为平移和缩放函数指定x值。因为坐标是乘以缩放因子(scale factor)的,所以平移(translate)的无操作值是0,而缩放(scale)的无操作值是1。scale(0)转换会把您的形状折叠到一个点(因为每个坐标乘以0都会变成0)。
样例6-8 采用了这种变换,在图6-14中生成了一个直角梯形。
Example 6-8 Transformed Cartesian coordinates
<svg width="200px" height="200px" viewBox="0 0 200 200"
xmlns="http://www.w3.org/2000/svg">
<g transform="translate(0,100) scale(1,-1)">
<!-- axes -->
<line x1="0" y1="0" x2="100" y2="0" style="stroke: black;"/>
<line x1="0" y1="0" x2="0" y2="100" style="stroke: black;"/>
<!-- trapezoid -->
<polygon points="40 40, 100 40, 70 70, 40 70"
style="fill: gray; stroke: black;"/>
</g>
</svg>
The rotate Transformation
通过指定的角度(angle)旋转坐标系统,这也是有可能的。在默认坐标系统中,当你顺时针旋转时,角度会增加,在水平线上时,它的角度是0,如图6-15所示。
除非您另外指定,否则旋转中心(中枢轴点的术语)被默认为(0,0)。样例6-9显示了一个绘画的灰色矩形,在坐标系同旋转45度后,以黑色作为填充色再绘制出这个矩形。坐标轴页显示出来,以方便参考。图6-16 显示了结果。如果你对矩形移动感到惊讶,你大可不必。记住,如图6-17所示,整个坐标系都被旋转了。
Example 6-9.Rotation around the origin
http://oreillymedia.github.io/svg-essentials-examples/ch06/rotate.html
<!-- axes -->
<polyline points="100 0, 0 0, 0 100" style="stroke: black; fill: none;"/>
<!-- normal and rotated square -->
<rect x="70" y="30" width="20" height="20" style="fill: gray;"/>
<rect x="70" y="30" width="20" height="20" transform="rotate(45)" style="fill: black;"/>
绝大多数时候,你不会想让整个坐标系围绕原点旋转。你想让一个对象围绕原点以外的点旋转。你可以通过这一系列的变换来实现:translate(centerX,centerY)rotate(angle)translate(-centerX,-centerY)。为了让这个常用的任务更简单,SVG 提供了另外一个版本的旋转(rotate)。在第二种形式的旋转变换中,你指定了你想要旋转的角度(angle)和中心点(center point)。
- rotate(angle, centerX, centerY)
它的作用是建立一个新的坐标系统,圆点在 centerX 和 centerY 指定的位置,以它为中心座旋转,然后重新建立原来的坐标。样例6-10 显示了用这种形式的旋转来创建一个箭头的多个副本,如图6-18所示。
Example 6-10. Rotation around a center point
http://oreillymedia.github.io/svg-essentials-examples/ch06/rotate-center.html
<!-- center of rotation -->
<circle cx="50" cy="50" r="3" style="fill: black;"/>
<!-- non-rotated arrow -->
<g id="arrow" style="stroke: black;">
<line x1="60" y1="50" x2="90" y2="50"/>
<polygon points="90 50, 85 45, 85 55"/>
</g>
<!-- rotated around center point -->
<use xlink:href="#arrow" transform="rotate(60, 50, 50)"/>
<use xlink:href="#arrow" transform="rotate(-90, 50, 50)"/>
<use xlink:href="#arrow" transform="rotate(-150, 50 50)"/>
Technique: Scaling Around a Center Point
既然可以围绕一个点做旋转,而不是围绕原点做旋转,那么没有理由不可以围绕一个指定的点做缩放。你当然可以这样做,然而,用一系列简单的变换制作同心符号。以给定的比例围绕一个中心点缩放一个对象。可以这样做:
translate(-centerX*(factor-1), -centerY*(factor-1))
scale(factor)
你可能还想将笔触的宽度除以比例系数,这样当对象变大时,对象的轮廓任然保持不变。样例6-11绘制了一系列的同心矩形,结果如图6-19所示:
Example 6-11. Scaling around a center point
<!-- center of scaling -->
<circle cx="50" cy="50" r="2" style="fill: black;"/>
<!-- non-scaled rectangle -->
<g id="box" style="stroke: black; fill: none;">
<rect x="35" y="40" width="30" height="20"/>
</g>
<use xlink:href="#box" transform="translate(-50,-50) scale(2)"
style="stroke-width: 0.5;"/>
<use xlink:href="#box" transform="translate(-75,-75) scale(2.5)"
style="stroke-width: 0.4;"/>
<use xlink:href="#box" transform="translate(-100,-100) scale(3)"
style="stroke-width: 0.33;"/>
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jHIg5MVR-1633927522345)(uploads/d0912135f19e2590f4069221131070db/image.png)]
The skewX and skewY Transformations
SVG 还有另外两种变换: skewX 和 skewY, 让其中一个轴倾斜。一般的形式是 skewX(angle) 和 skewY(angle)。 skewX 变换以指定的角度压缩(pushes)所有的x坐标,而y坐标保持不变。skewY 变换倾斜y坐标,而x坐标保持不边,如图6-20所示,它是用用例6-12中的代码绘制出来的。
Example 6-12.skewX and skewY
http://oreillymedia.github.io/svg-essentials-examples/ch06/skew.html
<!-- guide lines -->
<g style="stroke: gray; stroke-dasharray: 4 4;">
<line x1="0" y1="0" x2="200" y2="0"/>
<line x1="20" y1="0" x2="20" y2="90"/>
<line x1="120" y1="0" x2="120" y2="90"/>
</g>
<g transform="translate(20, 0)">
<g transform="skewX(30)">
<polyline points="50 0, 0 0, 0 50"
style="fill: none; stroke: black; stroke-width: 2;"/>
<text x="0" y="60">skewX</text>
</g>
</g>
<g transform="translate(120, 0)">
<g transform="skewY(30)">
<polyline points="50 0, 0 0, 0 50"
style="fill: none; stroke: black; stroke-width: 2;"/>
<text x="0" y="60">skewY</text>
</g>
</g>
-
- 这些虚线在任何变换之前,在默认的坐标系统中绘制。
-
- 将会移动整个倾斜的包(package)到期望的位置。
-
- 让x-坐标倾斜30度。这种变换不会修改原点,在新的坐标系统中,原点的位置仍然是(0,0).
-
- 为了让事情简单,我们在原点绘制了一个对象。
-
- 第 9 章将会讲解关于文本的细节。
-
- 这些元素以同样的方式组织起来,只不过是倾斜y坐标。
注意,skewX 让水平线保持不变,而skewY让垂直线保持不变。如图。
Transformation Reference Summary
表6-1 给出了SVG中可用的变换的快速总结。
Table 6-1. SVG transformations
变换 | 描述 |
---|---|
translate(x,y) | 根据给定的x和y的数量移动用户坐标系统。注意:如果你不指定y值,默认的值就是0。 |
scale(xFactor,yFactor) | 根据给定的 xFactor 和 yFactor 乘以所有的用户坐标系统。给出的因子可以是正数或者负数。 |
scale(factor) | 等价于scale(factor, factor) |
rotate(angle) | 以给定的 angle 旋转用户坐标。旋转的中心点是原点(0,0)。在默认坐标系统中,角度随着你往顺时针方向旋转而增加,以水平线所在的角度为0度 |
rotate(angle, centerX, centerY) | 以给定的 angle 旋转用户坐标。旋转的中心通过 centerX 和 centerY 指定。 |
skewX(angle) | 根据给定的 angle 倾斜 x轴坐标。 从视觉上看,这样做形成了一个角度的垂直线。 |
skewY(angle) | 根据给定的 angle 倾斜 y轴坐标。 从视觉上看,这样做形成了一个角度的水平线。 |
matrix(a b c d e f) | 以变换矩阵的形式指定一个变换,变换矩阵有六个值,参考 Appendix D |
CSS Transformations and SVG
在撰写本文时,CSS还有一个变换模块的工作草案,working draft of a transformations module。因为这是一个工作草案,细节可能会变动,浏览器的支持也可能变动。如果你已经使用过 CSS 的变换。和 SVG 相比,有一些重要的不同点如下:
- SVG 1.1 变换以用户单位或隐式度为单位。CSS 变换使用 CSS 长度和 角度单位。尽管该规范在应用于 SVG 元素时也隐含了用户单位。
- SVG 1.1 变换是结构属性,而 CSS 变换能够通过样式表指定。样式表规范覆盖了属性值。
- 在 CSS 中,变换类型和左括号之间不能有空格,并且必须使用逗号分隔数值。
- CSS 变换包含一个独立的属性来指定旋转的原点和缩放比例。在SVG中,旋转的原点是 rotate() 函数的一部分, 你不能指定缩放的原点。
- CSS 变换包含了3D 效果。
- [8] All the figures in this chapter are static pictures. This one shows two squares (one rotated and one unrotated). To
show an animation of a rotating square, use , which we will discuss in Chapter 12, in The
Element. - [9] This is also a static picture, a “square bull’s-eye.” If you want to show an animation of an expanding square, you’ll
use , which we will discuss in Chapter 12, in The Element.