SVG中的Transform详解---平移、旋转和缩放

想做一个svg的动画,但是却卡在了坐标变换,查了好久的资料,终于找到一篇讲得清楚的教程。

原文地址: css-tricks.com/transforms-…

下面文章是基于这篇教程,最后的案例也是来自于它。

前言

和普通的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 属性,变换函数的参数都只能是数字,不能带单位。而rotateskew中角度的参数默认以deg为单位。

普通的HTML元素和SVG元素在Transform形状变换方面最主要的区别,在于本地坐标系统的不同。 本地坐标系统的原点决定了变换的参考点。

对于普通的HTML元素,本地坐标系统的原点,默认值是元素自身在x、y方向的中心位置50% 50%(这里仅考虑二维平面),也就是元素的旋转、移位、缩放等操作都是以元素自身在x、y方向的中心位置进行的。

而SVG元素,本地坐标系统的原点,在该SVG元素没有进行任何形状变换的情况下,是在SVG画布0 0的位置(默认是<svg>元素的左上角)。

image.png

transform变换是怎么发生的?

translate--平移

平移,指的是该元素上所有的点相对于变换参考点沿着相同的方向,移动相同的距离。

image.png

上图是元素作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--旋转

一个图形在平面上的旋转有两个决定因素:参考点和旋转角度(角度的正负决定旋转方向)。

参考点的理解可以类比这些图钉:每一张卡片可以围绕固定它的图钉旋转,图钉就是卡片的旋转变换参考点。

image.png

这种情况下参考点是图形中的某一点,也有可能是图形之外的某一点。比如下面这种情况:

image.png

一个图形可以选择平面中任意一点作为旋转变换参考点。而如果两次旋转,初始位置相同,旋转角度相同,但参考点不同,最终位置就会不同。

image.png

上图是普通的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>
<svg width="200" height="200">
   <!--未指定本次旋转的参考点,绕着左上角旋转-->
   <rect x="0" y="0" width="100" height="100" fill="yellow" transform="rotate(45)"/>
</svg>
<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>
复制代码

image.png

我们终于GET了第一种指定变换参考点的方法,可惜它是一次性的,它只能指定本次旋转的参考点。

当我们连续进行两次旋转,一次顺时针,一次逆时针,角度相同。

<svg width="200" height="200">
    <rect x="0" y="0" width="100" height="100" fill="yellow" transform="rotate(45) rotate(-45)"/>
</svg>
<svg width="200" height="200">
    <rect x="0" y="0" width="100" height="100" fill="yellow" transform="rotate(45,50,50) rotate(-45)" />
</svg>
复制代码

image.png 可以看到第三个矩形,并未回归原来位置,因为第一次旋转的参考点为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>
<svg width="200" height="200">
    <rect x="0" y="0" width="100" height="100" fill="yellow" transform="rotate(45)"/>
</svg>
复制代码

image.png

transform-origin的取值有很多,参考 MDN,但是对于SVG元素,transform-origin: right top transform-origin: 50% 50%这些写法都是针对<svg>元素(上面的蓝色边框区域),比如transform-origin: 50% 50%,其实是蓝色边框区域的中心点。而要定位到黄色矩形的中心点,需要写transform-origin: 50px 50px这种绝对数值。

scale -- 缩放

缩放,改变的是,图形上所有的点,与变换参考点之间的距离。 image.png

推导过程(以上图右边为例):选取图形上任意一点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)'/>
复制代码
  1. 根据矩形的x、y、width、height确定矩形的中心点(140,105)。

  2. translate(140 105)将矩形向右移动140个单位长度Ux,向下移动105个单位长度Uy(最初Ux=Uy=1),同时变换参考点也作了同样的移动,变成了(140, 105)。

  3. 相对新的变换参考点,scale(2 1.5)将矩形在X、Y轴分别放大2倍和1.5倍,同时X、Y轴的单位长度也分别放大2倍和1.5倍,此时 Ux=2,Uy=1.5

  4. translate(-140 -105)将矩形向左移动140个单位长度,向上移动105个单位长度,此时单位长度已经变化,所以移动的距离其实是(140 * 2,105 * 1.5)。这也为什么第一个translate(140 105)和第二个translate(-140 -105)移动距离不相等的原因。

cover.png

translate、scale、rotate都会改变该元素的本地坐标系统,这一点对于普通的HTML元素和SVG元素都是一样,毕竟,他们的区别只在于本地坐标系统原点的不同,变换的原理是相同的。

以普通的HTML元素为例:

div { 
    transform-origin: right bottom; 
    transform: 
        rotate(90deg) 
        translate(0, -100%) 
        rotate(90deg) 
        translate(0, 100%); 
}
复制代码

cover.png

  1. 最初的变换参考点是元素的右下角
  2. rotate(90deg)元素顺时针旋转90deg,同时本地坐标系统也顺时针旋转90deg,X轴正方向向下,Y轴正方向向左。
  3. translate(0, -100%)向Y轴负方向移动矩形宽度的距离,也就是向右移动,同时变换参考点也作相同的移动。
  4. rotate(90deg)元素再次顺时针旋转90deg,同时本地坐标系统也再次顺时针旋转90deg,X轴正方向向左,Y轴正方向向上。
  5. 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>元素宽高(heightwidth属性)限制的区域。

理解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>
</svg>
<svg>
   <rect x='0' y='0' width='150' height='80' fill="orange"></rect>
</svg>
复制代码

image.png

这时,就需要设置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画布的原点,所以此时矩形完全居中。

image.png

下面是一个旋转的案例:

<svg viewBox='-140 -105 650 350'> 
    <rect x='-75' y='-40' width='150' height='80' transform='rotate(45)'/> 
</svg>
复制代码

viewbox.gif

综合案例

下面这个demo就将通过设置svg标签的viewBox属性,将SVG画布的原点移到可见区域的中心,里面所有的SVG元素都将绕中心作变换。

最终效果: star.gif

源码如下:

<!DOCTYPE html>
<html lang="en">
<head>
    <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;
        }
        svg{
            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>
<body>
    <svg viewBox='-512 -512 1024 1024' height="300" width="300">
        <defs> 
          <polygon id='star' points='250,0 64,64 0,250 -64,64 -250,0 -64,-64 0,-250 64,-64'/>
        </defs>   
        <use href='#star'/>
        <use href='#star'/>
        <use href='#star'/> 
    </svg>
</body>
</html>
复制代码

相关文章:

一个卡牌小游戏入门CSS3的3D变换

svg从入门到图标绘制和组件封装

CSS动画基础知识

猜你喜欢

转载自juejin.im/post/7078965147611430949
今日推荐