Detailed explanation of Transform in SVG - translation, rotation and scaling

I want to make an SVG animation, but I am stuck in the coordinate transformation. After checking the information for a long time, I finally found a clear tutorial.

Original address: css-tricks.com/transforms-…

The following article is based on this tutorial, and the final example is also derived from it.

foreword

Like ordinary HTML elements, SVG elements can be transformtransformed by shape, but many of the details are quite different from ordinary HTML elements.

SVG elements, SVG elements in the original text, refer to <svg>various shape elements included, <rect>, <circle>, <line>, <polygon>, <path>etc. For convenience, they are translated as " SVG elements " here and below, and the " <svg>elements " in the text refer specifically to <svg>tags representing Elements. It's too round! ! You all know what I mean--

For SVG elements, transform has two usages, one is the transform attribute written in the SVG element tag, and the other is the transform written in the CSS style.

//SVG元素标签里写的 transform 属性
<rect transform="rotate(45) scale(1.2)" width="100" height="100" fill="yellow" />

//CSS样式里写的 transform
rect{
    transform: rotate(20deg) scale(1.2);
}
复制代码

The difference between the two is: first, the transform written in CSS style is not supported in older versions of IE. If you want to be compatible with this part of the browser, you can select the transform attribute written in the SVG element tag; secondly, if it is an SVG element The transform attribute written in the tag and the parameters of the transform function can only be numbers, not units. And the parameters of the rotateand skewmiddle angle default degto units.

The main difference between ordinary HTML elements and SVG elements in Transform shape transformation is the difference in the local coordinate system. The origin of the local coordinate system determines the reference point for the transformation.

对于普通的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>
复制代码

Set the coordinates of the upper left corner (-140, -105), go 280 to the right, and go down 210. This area is the "new visible area", the graphics drawn in this area will be fully displayed, and the rectangle is in this area. . And, the center point of the visible area happens to be the origin of the SVG canvas at this time, so the rectangle is completely centered at this time.

image.png

Here's a rotated case:

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

viewbox.gif

Comprehensive case

The following demo will viewBoxmove the origin of the SVG canvas to the center of the visible area by setting the attributes of the svg tag, and all the SVG elements in it will be transformed around the center.

final effect:star.gif

The source code is as follows:

<!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>
复制代码

related articles:

A 3D transformation of CSS3 for getting started with a card game

SVG from entry to icon drawing and component encapsulation

CSS Animation Basics

Guess you like

Origin juejin.im/post/7078965147611430949