SVG中的Transform详解---平移、旋转和缩放
想做一个svg的动画,但是却卡在了坐标变换,查了好久的资料,终于找到一篇讲得清楚的教程。
原文地址: https:// css-tricks.com/transfor ms-on-svg-elements/
下面文章是基于这篇教程,最后的案例也是来自于它。
前言
和普通的HTML元素一样,SVG元素可以通过
transform
进行形状变换,但其中的很多细节,却和普通的HTML元素变换差异较大。
SVG元素,原文中SVG elements,指的是<svg>
包含的各种形状元素,<rect>
、<circle>
、<line>
、<polygon>
、<path>
等,为了方便,此处以及下文都将其翻译“ SVG元素 ”,而文中的“<svg>
元素 ”特指<svg>
标签代表的元素。太绕了!!大家懂我的意思就好--
对于SVG元素,transform 有两种用法,一个是在 SVG元素标签 里写的 transform 属性、另一种是在 CSS 样式里写的 transform。
//SVG元素标签里写的 transform 属性
<rect transform="rotate(45) scale(1.2)" width="100" height="100" fill="yellow" />
//CSS样式里写的 transform
rect{
transform: rotate(20deg) scale(1.2);
}
两者的不同之处,首先,CSS样式里写的 transform 在版本较老的IE里面不支持,如果要兼容这部分浏览器,可以选择 SVG元素标签里写的 transform 属性;其次,如果是SVG元素标签里写的 transform 属性,变换函数的参数都只能是数字,不能带单位。而
rotate
和
skew
中角度的参数默认以
deg
为单位。
普通的HTML元素和SVG元素在Transform形状变换方面最主要的区别,在于本地坐标系统的不同。 本地坐标系统的原点决定了变换的参考点。
对于普通的HTML元素,本地坐标系统的原点,默认值是元素自身在x、y方向的中心位置
50% 50%
(这里仅考虑二维平面),也就是元素的旋转、移位、缩放等操作都是以元素自身在x、y方向的中心位置进行的。
而SVG元素,本地坐标系统的原点,在该SVG元素没有进行任何形状变换的情况下,是在
SVG画布
的
0 0
的位置(默认是
<svg>
元素的左上角)。
transform变换是怎么发生的?
translate--平移
平移,指的是该元素上所有的点相对于变换参考点沿着相同的方向,移动相同的距离。
上图是元素作
transform: translate(295px,115px)
平移变换,左边是普通的HTML元素,右边是SVG元素。
虽然变换参考点不同,但结果一样。实际上, 平移的结果跟XY轴的方向、单位长度有关 。
普通的HTML元素和SVG元素的坐标系统都是:向右为X正方向,向下为Y正方向。
translate(295px,115px)
要执行的操作是:向右移295px,向下移115px。变换参考点是平面上的任何一点,所得到的结果都一样。但假如坐标系统向右为X正方向,向上为Y正方向,那么
translate(295px,115px)
就应该:向右移295px,向上移115px。单位长度的影响会在后面讨论。
我们前面提到,对于SVG元素,transform 可以写在 CSS 样式里,也可以写在SVG元素标签的属性上。
//CSS样式支持的语法
//tx,ty分别是X、Y方向的平移量,带单位,如transform: translate(20px)
transform: translateX(tx)
transform: translateY(ty)
transform: translate(tx[, ty])
//只写一个值时,默认是X方向的平移量,Y方向默认是0
//如果只在Y方向偏移,要写成:transform: translate(0, 20px)
//SVG元素属性支持的语法
<rect ... transform="translate(tx[ ty])" />
<rect ... transform="translate(tx[, ty])" />
与css里的写法区别:
1、不支持translateX、translateY
2、tx、ty只能是数字,不能带单位
3、tx、ty之间可以是空格或者逗号
transform="translate(10,20)" 或者 transform="translate(10 20)"
***/
rotate--旋转
一个图形在平面上的旋转有两个决定因素:参考点和旋转角度(角度的正负决定旋转方向)。
参考点的理解可以类比这些图钉:每一张卡片可以围绕固定它的图钉旋转,图钉就是卡片的旋转变换参考点。
这种情况下参考点是图形中的某一点,也有可能是图形之外的某一点。比如下面这种情况:
一个图形可以选择平面中任意一点作为旋转变换参考点。而如果两次旋转,初始位置相同,旋转角度相同,但参考点不同,最终位置就会不同。
上图是普通的HTML元素(左)和SVG元素(右)作
transform:rotate(45deg)
的结果。
如上所示,普通的HTML元素的变换参考点是元素自身在x、y方向的中心位置,而SVG元素的参考点是svg画布的原点。
//CSS样式支持的语法
transform: rotate(angle)
//angle是旋转的角度,带单位(deg、rad、turn、grad)。
//angle的值也可以是由calc()计算而来,比如transform: rotate(calc(.25turn - 30deg))
//angle的值若为正,则是顺时针旋转,若为负,则是逆时针旋转。
//元素transform属性支持的语法
<rect ... transform="rotate(angle[ x y])" />
与css里的写法区别:
1、rotate的参数不只是angle,多了一对可选参数x,y
2、angle,x,y只能是数字,不能带单位,angle是以deg来衡量。
3、angle,x,y之间可以是空格或者逗号
***/
SVG元素transform属性多出来的可选参数x,y,用于指定 本次旋转的参考点 。x,y必须成对出现,如果只写一个,则是无效语法。
既然能够指定旋转的参考点,那么SVG元素围绕自身中心进行旋转该怎么设置呢?
<svg width="200" height="200">
<rect x="0" y="0" width="100" height="100" fill="yellow"/>
<svg width="200" height="200">
<!--未指定本次旋转的参考点,绕着左上角旋转-->
<rect x="0" y="0" width="100" height="100" fill="yellow" transform="rotate(45)"/>
<svg width="200" height="200">
<!--指定本次旋转的参考点为rect的中心坐标(50,50),绕着这一点旋转-->
<rect x="0" y="0" width="100" height="100" fill="yellow" transform="rotate(45,50,50)" />
</svg>
我们终于GET了第一种指定变换参考点的方法,可惜它是一次性的,它只能指定本次旋转的参考点。
当我们连续进行两次旋转,一次顺时针,一次逆时针,角度相同。
<svg width="200" height="200">
<rect x="0" y="0" width="100" height="100" fill="yellow" transform="rotate(45) rotate(-45)"/>
<svg width="200" height="200">
<rect x="0" y="0" width="100" height="100" fill="yellow" transform="rotate(45,50,50) rotate(-45)" />
</svg>
可以看到第三个矩形,并未回归原来位置,因为第一次旋转的参考点为rect的中心坐标,第二次未指定,旋转参考点又变成了默认的左上角。
如果想每一次的旋转都围绕元素中心点,又不想每一次都指定(x,y),可以借助
transform-origin
。
rect{
transform-origin: 50px 50px; //矩形的中心坐标
<svg width="200" height="200">
<rect x="0" y="0" width="100" height="100" fill="yellow"/>
<svg width="200" height="200">
<rect x="0" y="0" width="100" height="100" fill="yellow" transform="rotate(45)"/>
</svg>
transform-origin
的取值有很多,参考
MDN
,但是对于SVG元素,
transform-origin: right top
、
transform-origin: 50% 50%
这些写法都是针对
<svg>
元素(上面的蓝色边框区域),比如
transform-origin: 50% 50%
,其实是蓝色边框区域的中心点。而要定位到黄色矩形的中心点,需要写
transform-origin: 50px 50px
这种绝对数值。
scale -- 缩放
缩放,改变的是,图形上所有的点,与变换参考点之间的距离。
推导过程(以上图右边为例):选取图形上任意一点B,与变换参考点A连接成一条线AB(用向量AB描述更准确),向量AB在XY轴的分量分别为(x,y)。如果图形作缩放变换
transform: scale(sx, xy)
,向量AB在XY轴的分量就会变为(sx.x,sy.y),这时原来的B点,就移到了新的newB点。缩放变换就是图形上所有的点都作上述移动的结果。
上述
transform: scale(sx, sy)
,我们假设了
sx>1,sy>1
。如果
sx,sy
中谁小于1(准确来说是绝对值小于1),就会导致图形在那个方向压缩,而如果
sx,sy
中谁小于0,就会导致图形相对变换参考点在那个方向进行反转。推导过程和上面一样,大家可以推导一遍加深理解。
由缩放的原理可以知道,缩放的结果是受变换参考点影响的。上图是普通的HTML元素(左)和SVG元素(右)作
transform: scale(sx, sy)
的结果。
//CSS样式支持的语法
transform: scaleX(sx)
transform: scaleY(sy)
transform: scale(sx[, sy])
//sx,sy表示缩放的倍数,不带单位
//scaleX(sx)、scaleY(sy)分别指定X、Y方向的放缩,scaleX(sx)等同于scale(sx,1),scaleY(sy)等同于scale(1,sy)
//scale(sx[, sy])第二个参数可选,如果省略,表示sy和sx相同,等比例缩放。
//SVG元素属性支持的语法
<rect ... transform="scale(sx[ sy])" />
与css里的写法区别:
1、不支持scaleX、scaleY
2、tx、ty之间可以是空格或者逗号
***/
对于SVG元素,如果要以元素自身中心点来缩放,我们可以像上面rotate变换那样在css样式中指定
transform-origin
。
而另一种更通用灵活的方法就是链式变换。
链式变换
我们知道在变换中很重要的因素就是本地坐标系统,它的XY轴方向、原点位置、单位长度,直接影响着最终的结果。
那么一个元素的本地坐标系统是否是一直不变的呢(具体来说就是XY轴方向、原点和单位长度)?
实际上,translate、scale、rotate都会改变该元素的本地坐标系统:translate会改变本地坐标系统的原点位置,rotate会改变本地坐标系统的XY轴方向,scale会改变本地坐标系统XY轴方向的单位长度。
而我们要记住的是:每次变换参考点永远是本地坐标系统的原点(在没有通过
transform-origin
指定的情况下),涉及到(x,y)坐标值的变化永远是在XY轴方向的度量(而非水平或垂直方向),距离的计算是基于单位长度的计算。按照这个规则,我们来分析一下下面的链式变换。
<rect x='65' y='65' width='150' height='80' transform='translate(140 105) scale(2 1.5) translate(-140 -105)'/>
- 根据矩形的x、y、width、height确定矩形的中心点(140,105)。
-
translate(140 105)
将矩形向右移动140个单位长度Ux,向下移动105个单位长度Uy(最初Ux=Uy=1),同时变换参考点也作了同样的移动,变成了(140, 105)。 -
相对新的变换参考点,
scale(2 1.5)
将矩形在X、Y轴分别放大2倍和1.5倍,同时X、Y轴的单位长度也分别放大2倍和1.5倍,此时 Ux=2,Uy=1.5 -
translate(-140 -105)
将矩形向左移动140个单位长度,向上移动105个单位长度,此时单位长度已经变化,所以移动的距离其实是(140 * 2,105 * 1.5)。这也为什么第一个translate(140 105)
和第二个translate(-140 -105)
移动距离不相等的原因。
translate、scale、rotate都会改变该元素的本地坐标系统,这一点对于普通的HTML元素和SVG元素都是一样,毕竟,他们的区别只在于本地坐标系统原点的不同,变换的原理是相同的。
以普通的HTML元素为例:
div {
transform-origin: right bottom;
transform:
rotate(90deg)
translate(0, -100%)
rotate(90deg)
translate(0, 100%);
}
- 最初的变换参考点是元素的右下角
-
rotate(90deg)
元素顺时针旋转90deg,同时本地坐标系统也顺时针旋转90deg,X轴正方向向下,Y轴正方向向左。 -
translate(0, -100%)
向Y轴负方向移动矩形宽度的距离,也就是向右移动,同时变换参考点也作相同的移动。 -
rotate(90deg)
元素再次顺时针旋转90deg,同时本地坐标系统也再次顺时针旋转90deg,X轴正方向向左,Y轴正方向向上。 -
translate(0, 100%)
向Y轴正方向移动矩形宽度的距离,也就是向上移动。
SVG元素以自身中心点进行变换的解决方案
到目前位置,我们知道的SVG元素以自身中心点进行变换的解决方案有:
1、
transform="rotate(angle[ x y])"
指定本次旋转的参考点。
<rect x="0" y="0" width="100" height="100" transform="rotate(45,50,50)" />
2、通过
transform-origin
指定变换参考点。
rect{
transform-origin: 50px 50px; //矩形的中心坐标
}
3、链式变换
<rect x="0" y="0" width="100" height="100" transform="translate(50 50) rotate(45) translate(-50 -50)"/>
还有一种更为简洁的方式,就是合理设置svg标签的
viewBox
属性。
我们知道,SVG元素本地坐标系统的原点,也就是变换参考点,在该SVG元素没有进行任何形状变换的情况下,是在
SVG画布
的
0 0
的位置,默认是
<svg>
元素的左上角。
我们可以把svg的绘制和显示分成两个部分:画布和可见区域。
画布可以看成是一个大大的二维平面(甚至可以是无限大的),以<svg>
元素的左上角为原点,在这个平面上的图形可以将坐标定位在平面的任意位置。
而可见区域,是指只有这个区域内的图形才会被显示出来,超出区域的会被截断。默认情况下,是<svg>
元素宽高(height
和width
属性)限制的区域。
理解svg的绘制和显示对于理解变换坐标系统至关重要,更多详细介绍可以参考 svg从入门到图标绘制和组件封装
既然变换参考点是
SVG画布
的
0 0
的位置,那么如果
图形的中心点
与
SVG画布
0 0
的位置重叠,那岂不就是图形以自身中心点进行变换。
对于一个长150,宽80的矩形,将其左上角坐标设为(-75,-40),那么它的中心点就与SVG画布的原点重合。
<rect x='-75' y='-40' width='150' height='80'>
但此时,坐标为负的那部分图形被裁剪,不会出现在可见区域。
<svg>
<rect x='-75' y='-40' width='150' height='80' fill="orange"></rect>
<rect x='0' y='0' width='150' height='80' fill="orange"></rect>
</svg>
这时,就需要设置svg标签的
viewBox
属性,来设置“新的可见区域”。原理在
svg从入门到图标绘制和组件封装
有非常详细的说明。
<svg viewBox='-140 -105 280 210' height="200" width="300">
<rect x='-75' y='-40' width='150' height='80' fill="orange"></rect>
</svg>
设置 左上角坐标(-140,-105),往右走280,往下走210 的这块区域为“新的可见区域”,绘制在这个区域里的图形将完全显示,而矩形在这个区域中。并且,此时可见区域的中心点恰好也在SVG画布的原点,所以此时矩形完全居中。
下面是一个旋转的案例:
<svg viewBox='-140 -105 650 350'>
<rect x='-75' y='-40' width='150' height='80' transform='rotate(45)'/>
</svg>
综合案例
下面这个demo就将通过设置svg标签的
viewBox
属性,将SVG画布的原点移到可见区域的中心,里面所有的SVG元素都将绕中心作变换。
最终效果:
源码如下:
<!DOCTYPE html>
<html lang="en">
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
body{
text-align: center;
background: #333;
@keyframes ani-1 {
0% { transform: scale(0); }
25% { transform: scale(1); }
50% { transform: rotate(120deg); }
75%, 100% {
transform: rotate(120deg) translate(13em) scale(.2);
@keyframes ani-2 {
0% { transform: scale(0); }
25% { transform: scale(1); }
50% { transform: rotate(240deg); }
75%, 100% {
transform: rotate(240deg) translate(13em) scale(.2);
@keyframes ani-3 {
0% { transform: scale(0); }
25% { transform: scale(1); }
50% { transform: rotate(360deg); }
75%, 100% {
transform: rotate(360deg) translate(13em) scale(.2);
use:nth-of-type(1){
fill: hsl(120, 100%, 80%);
animation: ani-1 4s linear infinite;
use:nth-of-type(2){
fill: hsl(240, 100%, 80%);
animation: ani-2 4s linear infinite;
use:nth-of-type(3){
fill: hsl(360, 100%, 80%);
animation: ani-3 4s linear infinite;
</style>
</head>
<svg viewBox='-512 -512 1024 1024' height="300" width="300">
<polygon id='star' points='250,0 64,64 0,250 -64,64 -250,0 -64,-64 0,-250 64,-64'/>
</defs>