在涉及到前端图形学的时候,几乎避免不了 transform 属性的应用。

transform 一共内置了五种不同大类的函数(矩阵变形、平移、缩放、旋转、倾斜,具体细节有九个),开发者经常容易被不同函数的组合变换,搞到晕头转向。

当面对需要精准定位的需求时,如果对 transform 的计算原理理解不透彻,就会导致代码冗长、复杂度增加,易读性也会迅速下降。

事实上,前端里的 transform 有很多种,比如 CSSSVG 中的 transform 属性就有些许不同。不过万变不离其宗,它们底层的数学原理大体是一致的。

所以为了方便描述,本篇这里以 SVG transform 为主。

一来,可以免去 CSS 中大量关于单位不同的换算,排开很多跟原理无关的细节;
二来,作为矢量格式的 SVG 足够精简,用来描述数学计算方式,矢量化参数拥有天生的优势;

① transform: matrix(a, b, c, d, e, f)

说到图形学,那必然会涉及到矩阵运算。

matrix 函数可以说是最本源的存在,如果将前端页面想象成一块画布,matrix 就是这块画布的改造者。只需要设定不同的参数,就可以用 matrix 将图形随意变换。

同时,matrix 函数还是其他四类功能函数的核心,这四类分别是平移、缩放、旋转、倾斜,他们的实现方式都可以用 matrix 等价替换。

matrix 函数的参数是一个 3x3 的方阵矩阵,只不过这个矩阵中只有六个变量,所以函数声明里显式的参数列表长度为 6

矩阵形式如下(假设为 M):

M = ( a c e b d f 0 0 1 ) M = \begin{pmatrix} a & c & e \\ b & d & f \\ 0 & 0 & 1 \\ \end{pmatrix} M=⎝⎛​ab0​cd0​ef1​⎠⎞​

怎么用呢?
答案是:矩阵乘法

假设页面上有一个点 point_old 的坐标为 ***(oldX, oldY)***,转换后新的点 point_new 坐标为 ***(newX, newY)***。

在运算过程中,点的矩阵描述方式如下:

p o i n t o l d = ( o l d X o l d Y 1 ) p o i n t n e w = ( n e w X n e w Y 1 ) point_{old} = \begin{pmatrix} oldX \\ oldY \\ 1 \end{pmatrix} \\ point_{new} = \begin{pmatrix} newX \\ newY \\ 1 \end{pmatrix} pointold​=⎝⎛​oldXoldY1​⎠⎞​pointnew​=⎝⎛​newXnewY1​⎠⎞​

计算方式为:

p o i n t n e w = M ∗ p o i n t o l d point_{new} = M * point_{old} pointnew​=M∗pointold​

( n e w X n e w Y 1 ) = ( a c e b d f 0 0 1 ) ( o l d X o l d Y 1 ) = ( a ∗ o l d X + c ∗ o l d Y + e b ∗ o l d X + d ∗ o l d Y + f 1 ) \begin{pmatrix} newX \\ newY \\ 1 \\ \end{pmatrix} = \begin{pmatrix} a & c & e \\ b & d & f \\ 0 & 0 & 1 \\ \end{pmatrix} \begin{pmatrix} oldX \\ oldY \\ 1 \\ \end{pmatrix} = \begin{pmatrix} a*oldX+c*oldY+e \\ b*oldX+d*oldY+f \\ 1 \\ \end{pmatrix} ⎝⎛​newXnewY1​⎠⎞​=⎝⎛​ab0​cd0​ef1​⎠⎞​⎝⎛​oldXoldY1​⎠⎞​=⎝⎛​a∗oldX+c∗oldY+eb∗oldX+d∗oldY+f1​⎠⎞​

所以:

p o i n t n e w { n e w X = a ∗ o l d X + c ∗ o l d Y + e n e w Y = b ∗ o l d X + d ∗ o l d Y + f point_{new} \begin{cases} newX = a*oldX + c*oldY + e \\ newY = b*oldX + d*oldY + f \\ \end{cases} pointnew​{newX=a∗oldX+c∗oldY+enewY=b∗oldX+d∗oldY+f​

在这六个参数中,ef 主要负责偏移量,其余 abcd 则代表不同的放大倍数。

现在我们知道,可以通过对这六个参数的控制,实现不同的效果了。

比如默认状态下,matrix(1, 0, 0, 1, 0, 0) 代表了什么也不动,因为套用上述计算公式,

( n e w X n e w Y 1 ) = ( 1 0 0 0 1 0 0 0 1 ) ( o l d X o l d Y 1 ) = ( 1 ∗ o l d X + 0 ∗ o l d Y + 0 0 ∗ o l d X + 1 ∗ o l d Y + 0 1 ) = ( o l d X o l d Y 1 ) \begin{pmatrix} newX \\ newY \\ 1 \\ \end{pmatrix} = \begin{pmatrix} 1 & 0 & 0 \\ 0 & 1 & 0 \\ 0 & 0 & 1 \\ \end{pmatrix} \begin{pmatrix} oldX \\ oldY \\ 1 \\ \end{pmatrix} = \begin{pmatrix} 1*oldX+0*oldY+0 \\ 0*oldX+1*oldY+0 \\ 1 \\ \end{pmatrix} = \begin{pmatrix} oldX \\ oldY \\ 1 \\ \end{pmatrix} ⎝⎛​newXnewY1​⎠⎞​=⎝⎛​100​010​001​⎠⎞​⎝⎛​oldXoldY1​⎠⎞​=⎝⎛​1∗oldX+0∗oldY+00∗oldX+1∗oldY+01​⎠⎞​=⎝⎛​oldXoldY1​⎠⎞​

结果可以发现,点坐标没有任何变换。

到这里,transform 的核心函数 matrix() 是如何计算的,应该已经清楚了。

那么接下来看看剩下其他所有的函数是如何实现和 matrix 转换的。

<h1>default</h1>
<svg x="0px" y="0px" width="600px" height="300px"><line label="axisX" fill="none" stroke="black" stroke-width="10" x1="0" y1="0" x2="600" y2="0" /><line label="axisY" fill="none" stroke="black" stroke-width="10" x1="0" y1="0" x2="0" y2="300" /><rect x="0" y="0" width="200" height="100" fill="red" opacity="0.9"></rect>
</svg>

② transform: translate(x)

translate 为平移函数,当只有一个参数时,表示图形水平移动了多少的距离。
即:
n e w X = x + o l d X newX = x + oldX newX=x+oldX

那么很简单的,构造矩阵 matrix(1, 0, 0, 1, x, 0) 即可实现 translate(x) 的效果:

( n e w X n e w Y 1 ) = ( 1 0 x 0 1 0 0 0 1 ) ( o l d X o l d Y 1 ) = ( 1 ∗ o l d X + 0 ∗ o l d Y + x 0 ∗ o l d X + 1 ∗ o l d Y + 0 1 ) = ( x + o l d X o l d Y 1 ) \begin{pmatrix} newX \\ newY \\ 1 \\ \end{pmatrix} = \begin{pmatrix} 1 & 0 & x \\ 0 & 1 & 0 \\ 0 & 0 & 1 \\ \end{pmatrix} \begin{pmatrix} oldX \\ oldY \\ 1 \\ \end{pmatrix} = \begin{pmatrix} 1*oldX+0*oldY+x \\ 0*oldX+1*oldY+0 \\ 1 \\ \end{pmatrix} = \begin{pmatrix} x + oldX\\ oldY \\ 1 \\ \end{pmatrix} ⎝⎛​newXnewY1​⎠⎞​=⎝⎛​100​010​x01​⎠⎞​⎝⎛​oldXoldY1​⎠⎞​=⎝⎛​1∗oldX+0∗oldY+x0∗oldX+1∗oldY+01​⎠⎞​=⎝⎛​x+oldXoldY1​⎠⎞​

<div>    <h1>transform: translate(x)</h1>    <svg x="0px" y="0px" width="600px" height="300px">        <line label="axisX" fill="none" stroke="black" stroke-width="10" x1="0" y1="0" x2="600" y2="0" />        <line label="axisY" fill="none" stroke="black" stroke-width="10" x1="0" y1="0" x2="0" y2="300" />        <rect x="0" y="0" width="200" height="100" fill="red" opacity="0.9" transform="translate(100)"></rect>   </svg><h1>transform: matrix(1, 0, 0, 1, x, 0)</h1>    <svg x="0px" y="0px" width="600px" height="300px">        <line label="axisX" fill="none" stroke="black" stroke-width="10" x1="0" y1="0" x2="600" y2="0" />        <line label="axisY" fill="none" stroke="black" stroke-width="10" x1="0" y1="0" x2="0" y2="300" />        <rect x="0" y="0" width="200" height="100" fill="red" opacity="0.9" transform="matrix(1,0,0,1,100,0)"></rect>    </svg>
</div>

③ transform: translate(x, y)

这里可以看做单一参数的 translate 函数的重载函数,第二个参数 y 值,代表在笛卡尔坐标系下的二维平面中,y 轴方向的平移运动。

即:
{ n e w X = x + o l d X n e w Y = y + o l d Y \begin{cases} newX = x + oldX \\ newY = y + oldY \end{cases} {newX=x+oldXnewY=y+oldY​

同理,可构造矩阵 matrix(1, 0, 0, 1, x, y) 实现 translate(x, y) 的效果:

( n e w X n e w Y 1 ) = ( 1 0 x 0 1 y 0 0 1 ) ( o l d X o l d Y 1 ) = ( 1 ∗ o l d X + 0 ∗ o l d Y + x 0 ∗ o l d X + 1 ∗ o l d Y + y 1 ) = ( x + o l d X y + o l d Y 1 ) \begin{pmatrix} newX \\ newY \\ 1 \\ \end{pmatrix} = \begin{pmatrix} 1 & 0 & x \\ 0 & 1 & y \\ 0 & 0 & 1 \\ \end{pmatrix} \begin{pmatrix} oldX \\ oldY \\ 1 \\ \end{pmatrix} = \begin{pmatrix} 1*oldX+0*oldY+x \\ 0*oldX+1*oldY+y \\ 1 \\ \end{pmatrix} = \begin{pmatrix} x + oldX \\ y + oldY \\ 1 \\ \end{pmatrix} ⎝⎛​newXnewY1​⎠⎞​=⎝⎛​100​010​xy1​⎠⎞​⎝⎛​oldXoldY1​⎠⎞​=⎝⎛​1∗oldX+0∗oldY+x0∗oldX+1∗oldY+y1​⎠⎞​=⎝⎛​x+oldXy+oldY1​⎠⎞​

<div>    <h1>transform: translate(x, y)</h1>    <svg x="0px" y="0px" width="600px" height="300px">       <line label="axisX" fill="none" stroke="black" stroke-width="10" x1="0" y1="0" x2="600" y2="0" />       <line label="axisY" fill="none" stroke="black" stroke-width="10" x1="0" y1="0" x2="0" y2="300" />      <rect x="0" y="0" width="200" height="100" fill="red" opacity="0.9" transform="translate(100,50)"></rect>   </svg><h1>transform: matrix(1, 0, 0, 1, x, y)</h1>  <svg x="0px" y="0px" width="600px" height="300px">   <line label="axisX" fill="none" stroke="black" stroke-width="10" x1="0" y1="0" x2="600" y2="0" />      <line label="axisY" fill="none" stroke="black" stroke-width="10" x1="0" y1="0" x2="0" y2="300" />     <rect x="0" y="0" width="200" height="100" fill="red" opacity="0.9" transform="matrix(1,0,0,1,100,50)"></rect>   </svg>
</div>

④ transform: scale(s)

scale 为缩放函数,当只有一个参数时,表示图形在水平和纵向两个轴上,实现等比例的放大缩小。

即:

{ n e w X = s ∗ o l d X n e w Y = s ∗ o l d Y \begin{cases} newX = s*oldX \\ newY = s*oldY \end{cases} {newX=s∗oldXnewY=s∗oldY​

由于这里是成比例放大,所以可得变换矩阵 ***matrix(s, 0, 0, s, 0, 0)***:

( n e w X n e w Y 1 ) = ( s 0 0 0 s 0 0 0 1 ) ( o l d X o l d Y 1 ) = ( s ∗ o l d X + 0 ∗ o l d Y + 0 0 ∗ o l d X + s ∗ o l d Y + 0 1 ) = ( s ∗ o l d X s ∗ o l d Y 1 ) \begin{pmatrix} newX \\ newY \\ 1 \\ \end{pmatrix}= \begin{pmatrix} s & 0 & 0 \\ 0 & s & 0 \\ 0 & 0 & 1 \\ \end{pmatrix} \begin{pmatrix} oldX \\ oldY \\ 1 \\ \end{pmatrix}= \begin{pmatrix} s*oldX+0*oldY+0 \\ 0*oldX+s*oldY+0 \\ 1 \\ \end{pmatrix} = \begin{pmatrix} s*oldX \\ s*oldY \\ 1 \\ \end{pmatrix} ⎝⎛​newXnewY1​⎠⎞​=⎝⎛​s00​0s0​001​⎠⎞​⎝⎛​oldXoldY1​⎠⎞​=⎝⎛​s∗oldX+0∗oldY+00∗oldX+s∗oldY+01​⎠⎞​=⎝⎛​s∗oldXs∗oldY1​⎠⎞​

<div>  <h1>transform: scale(s)</h1>  <svg x="0px" y="0px" width="600px" height="300px"> <line label="axisX" fill="none" stroke="black" stroke-width="10" x1="0" y1="0" x2="600" y2="0" />     <line label="axisY" fill="none" stroke="black" stroke-width="10" x1="0" y1="0" x2="0" y2="300" />      <rect x="0" y="0" width="200" height="100" fill="red" opacity="0.9" transform="scale(2)"></rect>  </svg><h1>transform: matrix(s, 0, 0, s, 0, 0)</h1>   <svg x="0px" y="0px" width="600px" height="300px">   <line label="axisX" fill="none" stroke="black" stroke-width="10" x1="0" y1="0" x2="600" y2="0" />    <line label="axisY" fill="none" stroke="black" stroke-width="10" x1="0" y1="0" x2="0" y2="300" />    <rect x="0" y="0" width="200" height="100" fill="red" opacity="0.9" transform="matrix(2,0,0,2,0,0)">   </rect>  </svg>
</div>

⑤ transform: scale(sx, sy)

这里同样的,也是拥有两个参数的重载函数,由此可以分开控制不同轴向的缩放倍率。

即:
{ n e w X = s x ∗ o l d X n e w Y = s y ∗ o l d Y \begin{cases} newX = sx*oldX \\ newY = sy*oldY \end{cases} {newX=sx∗oldXnewY=sy∗oldY​

同理可得变换矩阵 ***matrix(sx, 0, 0, sy, 0, 0)***:

( n e w X n e w Y 1 ) = ( s x 0 0 0 s y 0 0 0 1 ) ( o l d X o l d Y 1 ) = ( s x ∗ o l d X + 0 ∗ o l d Y + 0 0 ∗ o l d X + s y ∗ o l d Y + 0 1 ) = ( s x ∗ o l d X s y ∗ o l d Y 1 ) \begin{pmatrix} newX \\ newY \\ 1 \\ \end{pmatrix}= \begin{pmatrix} sx & 0 & 0 \\ 0 & sy & 0 \\ 0 & 0 & 1 \\ \end{pmatrix} \begin{pmatrix} oldX \\ oldY \\ 1 \\ \end{pmatrix} = \begin{pmatrix} sx*oldX+0*oldY+0 \\ 0*oldX+sy*oldY+0 \\ 1 \\ \end{pmatrix} = \begin{pmatrix} sx*oldX \\ sy*oldY \\ 1 \\ \end{pmatrix} ⎝⎛​newXnewY1​⎠⎞​=⎝⎛​sx00​0sy0​001​⎠⎞​⎝⎛​oldXoldY1​⎠⎞​=⎝⎛​sx∗oldX+0∗oldY+00∗oldX+sy∗oldY+01​⎠⎞​=⎝⎛​sx∗oldXsy∗oldY1​⎠⎞​

<div>   <h1>transform: scale(sx, sy)</h1>  <svg x="0px" y="0px" width="600px" height="300px">    <line label="axisX" fill="none" stroke="black" stroke-width="10" x1="0" y1="0" x2="600" y2="0" />     <line label="axisY" fill="none" stroke="black" stroke-width="10" x1="0" y1="0" x2="0" y2="300" />      <rect x="0" y="0" width="200" height="100" fill="red" opacity="0.9" transform="scale(0.5,2)"></rect>  </svg><h1>transform: matrix(sx, 0, 0, sy, 0, 0)</h1> <svg x="0px" y="0px" width="600px" height="300px">   <line label="axisX" fill="none" stroke="black" stroke-width="10" x1="0" y1="0" x2="600" y2="0" />     <line label="axisY" fill="none" stroke="black" stroke-width="10" x1="0" y1="0" x2="0" y2="300" />     <rect x="0" y="0" width="200" height="100" fill="red" opacity="0.9" transform="matrix(0.5,0,0,2,0,0)"></rect>   </svg>
</div>

⑥ transform: rotate(a)

rotate 为旋转函数,当参数个数为 1 时,表示以当前元素坐标系原点为旋转点,旋转角度为 a 度。

需要提前注意的是,这里的单位为 deg,角度制。

而在接下来换算成 matrix 的过程中,需要用到三角函数。

所以在数值上,需要将角度制,转换成弧度制:
a ′ = π 180 ∗ a a'=\frac{\pi}{180}*a a′=180π​∗a

此外,由于在二维平面旋转运动下,任意点到旋转圆心的距离不变。所以为了方便计算,我们在这里使用极坐标系,推导笛卡尔坐标系下物体运动的方式。

根据极坐标系,我们用有序数对 *(ρ, θ) * 表示任意点 P 的坐标,ρ 代表极径,θ 代表极角(弧度制)。

记为 P(ρ, θ)

那么,任意点旋转 a 角度(*a’ * 弧度)后的坐标即为:P(ρ, θ + a’)

利用坐标系间的映射关系:

{ X = ρ ∗ c o s ( θ ) Y = ρ ∗ s i n ( θ ) \begin{cases} X = \rho*cos(\theta) \\ Y = \rho*sin(\theta) \\ \end{cases} {X=ρ∗cos(θ)Y=ρ∗sin(θ)​

可得:
n e w P = o l d P ( ρ , θ + a ′ ) newP = oldP(\rho,\theta + a') newP=oldP(ρ,θ+a′)

{ n e w X = ρ ∗ c o s ( θ + a ′ ) n e w Y = ρ ∗ s i n ( θ + a ′ ) \begin{cases} newX = \rho*cos(\theta+a') \\ newY = \rho*sin(\theta+a') \\ \end{cases} {newX=ρ∗cos(θ+a′)newY=ρ∗sin(θ+a′)​

进一步展开可得:
n e w X = ρ ∗ c o s ( θ + a ′ ) = ρ ∗ c o s ( θ ) ∗ c o s ( a ′ ) − ρ ∗ s i n ( θ ) ∗ s i n ( a ′ ) = o l d X ∗ c o s ( a ′ ) − o l d Y ∗ s i n ( a ′ ) = c o s ( a ′ ) ∗ o l d X + ( − 1 ) ∗ s i n ( a ′ ) ∗ o l d Y \begin{aligned} newX &= \rho*cos(\theta+a') \\ &= \rho*cos(\theta)*cos(a')-\rho*sin(\theta)*sin(a') \\ &= oldX*cos(a')-oldY*sin(a') \\ &= cos(a')*oldX + (-1)*sin(a')*oldY \\ \end{aligned} newX​=ρ∗cos(θ+a′)=ρ∗cos(θ)∗cos(a′)−ρ∗sin(θ)∗sin(a′)=oldX∗cos(a′)−oldY∗sin(a′)=cos(a′)∗oldX+(−1)∗sin(a′)∗oldY​

n e w Y = ρ ∗ s i n ( θ + a ′ ) = ρ ∗ s i n ( θ ) ∗ c o s ( a ′ ) + ρ ∗ c o s ( θ ) ∗ s i n ( a ′ ) = o l d Y ∗ c o s ( a ′ ) + o l d X ∗ s i n ( a ′ ) = s i n ( a ′ ) ∗ o l d X + c o s ( a ′ ) ∗ o l d Y \begin{aligned} newY & = \rho*sin(\theta+a') \\ & = \rho*sin(\theta)*cos(a')+\rho*cos(\theta)*sin(a') \\ & = oldY * cos(a') + oldX * sin(a') \\ & = sin(a') * oldX + cos(a') * oldY \end{aligned} newY​=ρ∗sin(θ+a′)=ρ∗sin(θ)∗cos(a′)+ρ∗cos(θ)∗sin(a′)=oldY∗cos(a′)+oldX∗sin(a′)=sin(a′)∗oldX+cos(a′)∗oldY​

根据上式,可以推出变换矩阵为 matrix(cos(a’), sin(a’), -sin(a’), cos(a’), 0, 0)
( n e w X n e w Y 1 ) = ( c o s ( a ′ ) − s i n ( a ′ ) 0 s i n ( a ′ ) c o s ( a ′ ) 0 0 0 1 ) ( o l d X o l d Y 1 ) = ( c o s ( a ′ ) ∗ o l d X − s i n ( a ′ ) ∗ o l d Y + 0 s i n ( a ′ ) ∗ o l d X + c o s ( a ′ ) ∗ o l d Y + 0 1 ) = ( ρ ∗ c o s ( a ′ ) ∗ c o s ( θ ) − ρ ∗ s i n ( a ′ ) ∗ s i n ( θ ) ρ ∗ s i n ( a ′ ) ∗ c o s ( θ ) + ρ ∗ c o s ( a ′ ) ∗ s i n ( θ ) 1 ) = ( ρ ∗ c o s ( θ + a ′ ) ρ ∗ s i n ( θ + a ′ ) 1 ) \begin{aligned} \begin{pmatrix} newX \\ newY \\ 1 \\ \end{pmatrix} & = \begin{pmatrix} cos(a') & -sin(a') & 0 \\ sin(a') & cos(a') & 0 \\ 0 & 0 & 1 \\ \end{pmatrix} \begin{pmatrix} oldX \\ oldY \\ 1 \\ \end{pmatrix} \\ & = \begin{pmatrix} cos(a')*oldX-sin(a')*oldY+0 \\ sin(a')*oldX+cos(a')*oldY+0 \\ 1 \\ \end{pmatrix} \\ & = \begin{pmatrix} \rho*cos(a')*cos(\theta)-\rho*sin(a')*sin(\theta) \\ \rho*sin(a')*cos(\theta)+\rho*cos(a')*sin(\theta) \\ 1 \\\end{pmatrix} \\ & = \begin{pmatrix} \rho*cos(\theta + a') \\ \rho*sin(\theta + a') \\ 1 \\ \end{pmatrix} \end{aligned} ⎝⎛​newXnewY1​⎠⎞​​=⎝⎛​cos(a′)sin(a′)0​−sin(a′)cos(a′)0​001​⎠⎞​⎝⎛​oldXoldY1​⎠⎞​=⎝⎛​cos(a′)∗oldX−sin(a′)∗oldY+0sin(a′)∗oldX+cos(a′)∗oldY+01​⎠⎞​=⎝⎛​ρ∗cos(a′)∗cos(θ)−ρ∗sin(a′)∗sin(θ)ρ∗sin(a′)∗cos(θ)+ρ∗cos(a′)∗sin(θ)1​⎠⎞​=⎝⎛​ρ∗cos(θ+a′)ρ∗sin(θ+a′)1​⎠⎞​​

<div>  <h1>transform: rotate(a)</h1>  <svg x="0px" y="0px" width="600px" height="300px">   <line label="axisX" fill="none" stroke="black" stroke-width="10" x1="0" y1="0" x2="600" y2="0" />      <line label="axisY" fill="none" stroke="black" stroke-width="10" x1="0" y1="0" x2="0" y2="300" />     <rect x="0" y="0" width="200" height="100" fill="red" opacity="0.9" transform="rotate(30)"></rect>  </svg><h1>transform: matrix(cos(a'), sin(a'), -sin(a'), cos(a'), 0, 0)</h1>  <svg x="0px" y="0px" width="600px" height="300px">    <line label="axisX" fill="none" stroke="black" stroke-width="10" x1="0" y1="0" x2="600" y2="0" />      <line label="axisY" fill="none" stroke="black" stroke-width="10" x1="0" y1="0" x2="0" y2="300" />     <rect x="0" y="0" width="200" height="100" fill="red" opacity="0.9" transform="matrix(0.866025,0.5,-0.5,0.866025,0,0)"></rect> </svg>
</div>

⑦ transform: rotate(a, x, y)

rotate 函数被指定旋转点后,情况稍微复杂了一点。

由于函数本质上控制的是画布本身,也可以理解为坐标系本身。

所以,如果想要坐标系上的某一个图形围绕具体一个点旋转,则需要以下三个步骤:

第一、将旋转点从坐标系原点,移动至指定点;
第二、该指定点默认为坐标系原点,开始旋转;
第三、为了保持旋转时其他图案的不变,将坐标系原点从指定点移动回初始点位。

所以,通常指定点的旋转,会采用 <translate(x, y)><rotate(a)><translate(-x, -y)> 的方式。

translate 中的参数 xy 即为 rotate(a, x, y) 中的指定点坐标。

那么这种情况,应当如何用 matrix 描述呢?

我们假设上述三个变换矩阵分别为:
{ t r a n s l a t e ( x , y ) = T 1 = ( 1 0 x 0 1 y 0 0 1 ) r o t a t e ( a ) = R = ( c o s ( a ′ ) − s i n ( a ′ ) 0 s i n ( a ′ ) c o s ( a ′ ) 0 0 0 1 ) t r a n s l a t e ( − x , − y ) = T 2 = ( 1 0 − x 0 1 − y 0 0 1 ) \begin{cases} translate(x,y)= T_1 = \begin{pmatrix} 1 & 0 & x \\ 0 & 1 & y \\ 0 & 0 & 1 \\ \end{pmatrix} \\ rotate(a) = R = \begin{pmatrix} cos(a') & -sin(a') & 0 \\ sin(a') & cos(a') & 0 \\ 0 & 0 & 1 \\ \end{pmatrix} \\ translate(-x,-y)=T_2= \begin{pmatrix} 1 & 0 & -x \\ 0 & 1 & -y \\ 0 & 0 & 1 \\ \end{pmatrix} \end{cases} ⎩⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎨⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎧​translate(x,y)=T1​=⎝⎛​100​010​xy1​⎠⎞​rotate(a)=R=⎝⎛​cos(a′)sin(a′)0​−sin(a′)cos(a′)0​001​⎠⎞​translate(−x,−y)=T2​=⎝⎛​100​010​−x−y1​⎠⎞​​

则,根据函数执行方式可得矩阵计算方式为:
( n e w X n e w Y 1 ) = T 1 ∗ R ∗ T 2 ∗ ( o l d X o l d Y 1 ) \begin{pmatrix} newX \\ newY \\ 1 \\ \end{pmatrix} = T_1 * R * T_2 * \begin{pmatrix} oldX \\ oldY \\ 1 \\ \end{pmatrix} ⎝⎛​newXnewY1​⎠⎞​=T1​∗R∗T2​∗⎝⎛​oldXoldY1​⎠⎞​

即:
M = T 1 ∗ R ∗ T 2 = ( 1 0 x 0 1 y 0 0 1 ) ( c o s ( a ′ ) − s i n ( a ′ ) 0 s i n ( a ′ ) c o s ( a ′ ) 0 0 0 1 ) ( 1 0 − x 0 1 − y 0 0 1 ) = ( c o s ( a ′ ) − s i n ( a ′ ) x s i n ( a ′ ) c o s ( a ′ ) y 0 0 1 ) ( 1 0 − x 0 1 − y 0 0 1 ) = ( c o s ( a ′ ) − s i n ( a ′ ) − x ∗ c o s ( a ′ ) + y ∗ s i n ( a ′ ) + x s i n ( a ′ ) c o s ( a ′ ) − x ∗ s i n ( a ′ ) − y ∗ c o s ( a ′ ) + y 0 0 1 ) \begin{aligned} M & = T_1 * R * T_2 \\ & = \begin{pmatrix} 1 & 0 & x \\ 0 & 1 & y \\ 0 & 0 & 1 \\ \end{pmatrix} \begin{pmatrix} cos(a') & -sin(a') & 0 \\ sin(a') & cos(a') & 0 \\ 0 & 0 & 1 \\ \end{pmatrix} \begin{pmatrix} 1 & 0 & -x \\ 0 & 1 & -y \\ 0 & 0 & 1 \\ \end{pmatrix} \\ &= \begin{pmatrix} cos(a') & -sin(a') & x \\ sin(a') & cos(a') & y \\ 0 & 0 & 1 \\ \end{pmatrix} \begin{pmatrix} 1 & 0 & -x \\ 0 & 1 & -y \\ 0 & 0 & 1 \\ \end{pmatrix} \\ &= \begin{pmatrix} cos(a') & -sin(a') & -x*cos(a')+y*sin(a')+x \\ sin(a') & cos(a') & -x*sin(a')-y*cos(a')+y \\ 0 & 0 & 1 \end{pmatrix} \end{aligned} M​=T1​∗R∗T2​=⎝⎛​100​010​xy1​⎠⎞​⎝⎛​cos(a′)sin(a′)0​−sin(a′)cos(a′)0​001​⎠⎞​⎝⎛​100​010​−x−y1​⎠⎞​=⎝⎛​cos(a′)sin(a′)0​−sin(a′)cos(a′)0​xy1​⎠⎞​⎝⎛​100​010​−x−y1​⎠⎞​=⎝⎛​cos(a′)sin(a′)0​−sin(a′)cos(a′)0​−x∗cos(a′)+y∗sin(a′)+x−x∗sin(a′)−y∗cos(a′)+y1​⎠⎞​​

也就是说,变换矩阵为 :
matrix(cos(a’), sin(a’), -sin(a’), cos(a’), -xcos(a’)+ysin(a’)+x, -xsin(a’)-ycos(a’)+y)**

( n e w X n e w Y 1 ) = ( c o s ( a ′ ) − s i n ( a ′ ) − x ∗ c o s ( a ′ ) + y ∗ s i n ( a ′ ) + x s i n ( a ′ ) c o s ( a ′ ) − x ∗ s i n ( a ′ ) − y ∗ c o s ( a ′ ) + y 0 0 1 ) ( o l d X o l d Y 1 ) \begin{pmatrix} newX \\ newY \\ 1 \end{pmatrix} = \begin{pmatrix} cos(a') & -sin(a') & -x*cos(a')+y*sin(a')+x \\ sin(a') & cos(a') & -x*sin(a')-y*cos(a')+y \\ 0 & 0 & 1 \end{pmatrix} \begin{pmatrix} oldX \\ oldY \\ 1 \\ \end{pmatrix} ⎝⎛​newXnewY1​⎠⎞​=⎝⎛​cos(a′)sin(a′)0​−sin(a′)cos(a′)0​−x∗cos(a′)+y∗sin(a′)+x−x∗sin(a′)−y∗cos(a′)+y1​⎠⎞​⎝⎛​oldXoldY1​⎠⎞​

<div>  <h1>transform: rotate(a, x, y)</h1> <svg x="0px" y="0px" width="600px" height="300px">    <line label="axisX" fill="none" stroke="black" stroke-width="10" x1="0" y1="0" x2="600" y2="0" />      <line label="axisY" fill="none" stroke="black" stroke-width="10" x1="0" y1="0" x2="0" y2="300" />    <rect x="0" y="0" width="200" height="100" fill="red" opacity="0.9" transform="rotate(30,0,100)"></rect>  </svg><h1>transform: matrix(cos(a'), sin(a'), -sin(a'), cos(a'), -x*cos(a')+y*sin(a')+x, -x*sin(a')-y*cos(a')+y)</h1>  <svg x="0px" y="0px" width="600px" height="300px">   <line label="axisX" fill="none" stroke="black" stroke-width="10" x1="0" y1="0" x2="600" y2="0" />      <line label="axisY" fill="none" stroke="black" stroke-width="10" x1="0" y1="0" x2="0" y2="300" />       <rect x="0" y="0" width="200" height="100" fill="red" opacity="0.9" transform="matrix(0.866025, 0.5, -0.5, 0.866025, 50.0, 13.39746)"></rect> </svg>
</div>

⑧ transform: skewX(a)

skewX 表示的是 x 轴方向上的倾斜,同样这里将使用三角函数,也同样的,存在弧度制下的:
a ′ = π 180 ∗ a a'=\frac{\pi}{180}*a a′=180π​∗a

由于倾斜只发生在 x 轴方向,由此可得:

{ n e w X = Δ x + o l d X = t a n ( a ′ ) ∗ o l d Y + o l d X n e w Y = o l d Y \begin{cases} newX = \Delta x + oldX = tan(a')*oldY + oldX\\ newY = oldY \end{cases} {newX=Δx+oldX=tan(a′)∗oldY+oldXnewY=oldY​

故,变换函数为 matrix(1, 0, tan(a’), 1, 0, 0)
( n e w X n e w Y 1 ) = ( 1 t a n ( a ′ ) 0 0 1 0 0 0 1 ) ( o l d X o l d Y 1 ) = ( 1 ∗ o l d X + t a n ( a ′ ) ∗ o l d Y + 0 0 ∗ o l d X + 1 ∗ o l d Y + 0 1 ) = ( t a n ( a ′ ) ∗ o l d Y + o l d X o l d Y 1 ) \begin{aligned} \begin{pmatrix} newX \\ newY \\ 1 \\ \end{pmatrix} & = \begin{pmatrix} 1 & tan(a') & 0 \\ 0 & 1 & 0 \\ 0 & 0 & 1 \\ \end{pmatrix} \begin{pmatrix} oldX \\ oldY \\ 1 \\ \end{pmatrix} \\ & = \begin{pmatrix} 1*oldX+tan(a')*oldY+0 \\ 0*oldX+1*oldY+0 \\ 1 \\ \end{pmatrix}\\ &= \begin{pmatrix} tan(a')*oldY + oldX\\ oldY \\ 1 \\ \end{pmatrix} \end{aligned} ⎝⎛​newXnewY1​⎠⎞​​=⎝⎛​100​tan(a′)10​001​⎠⎞​⎝⎛​oldXoldY1​⎠⎞​=⎝⎛​1∗oldX+tan(a′)∗oldY+00∗oldX+1∗oldY+01​⎠⎞​=⎝⎛​tan(a′)∗oldY+oldXoldY1​⎠⎞​​

<div>   <h1>transform: skewX(a)</h1>   <svg x="0px" y="0px" width="600px" height="300px">     <line label="axisX" fill="none" stroke="black" stroke-width="10" x1="0" y1="0" x2="600" y2="0" />    <line label="axisY" fill="none" stroke="black" stroke-width="10" x1="0" y1="0" x2="0" y2="300" />     <rect x="0" y="0" width="200" height="100" fill="red" opacity="0.9" transform="skewX(30)"></rect>   </svg><h1>transform: matrix(1, 0, tan(a'), 1, 0, 0)</h1>  <svg x="0px" y="0px" width="600px" height="300px">   <line label="axisX" fill="none" stroke="black" stroke-width="10" x1="0" y1="0" x2="600" y2="0" />     <line label="axisY" fill="none" stroke="black" stroke-width="10" x1="0" y1="0" x2="0" y2="300" />     <rect x="0" y="0" width="200" height="100" fill="red" opacity="0.9" transform="matrix(1,0,0.577350,1,0,0)"></rect>  </svg>
</div>

⑨ transform: skewY(a)

skewY 表示的是 y 轴方向的倾斜,原理同上:
{ n e w X = o l d X n e w Y = Δ y + o l d Y = t a n ( a ′ ) ∗ o l d X + o l d Y \begin{cases} newX = oldX \\ newY = \Delta y + oldY = tan(a')*oldX + oldY \\ \end{cases} {newX=oldXnewY=Δy+oldY=tan(a′)∗oldX+oldY​

可得变换函数 matrix(1, tan(a’), 0, 1, 0, 0)

( n e w X n e w Y 1 ) = ( 1 0 0 t a n ( a ′ ) 1 0 0 0 1 ) ( o l d X o l d Y 1 ) = ( 1 ∗ o l d X + 0 ∗ o l d Y + 0 t a n ( a ′ ) ∗ o l d X + 1 ∗ o l d Y + 0 1 ) = ( o l d X t a n ( a ′ ) ∗ o l d X + o l d Y 1 ) \begin{aligned} \begin{pmatrix} newX \\ newY \\ 1 \\ \end{pmatrix} & = \begin{pmatrix} 1 & 0 & 0 \\ tan(a') & 1 & 0 \\ 0 & 0 & 1 \\ \end{pmatrix} \begin{pmatrix} oldX \\ oldY \\ 1 \\ \end{pmatrix} \\ & = \begin{pmatrix} 1*oldX + 0*oldY + 0 \\ tan(a')*oldX+1*oldY + 0 \\ 1 \\ \end{pmatrix}\\ &= \begin{pmatrix} oldX\\ tan(a')*oldX+oldY \\ 1 \\ \end{pmatrix} \end{aligned} ⎝⎛​newXnewY1​⎠⎞​​=⎝⎛​1tan(a′)0​010​001​⎠⎞​⎝⎛​oldXoldY1​⎠⎞​=⎝⎛​1∗oldX+0∗oldY+0tan(a′)∗oldX+1∗oldY+01​⎠⎞​=⎝⎛​oldXtan(a′)∗oldX+oldY1​⎠⎞​​

<div>   <h1>transform: skewY(a)</h1>   <svg x="0px" y="0px" width="600px" height="300px">     <line label="axisX" fill="none" stroke="black" stroke-width="10" x1="0" y1="0" x2="600" y2="0" />      <line label="axisY" fill="none" stroke="black" stroke-width="10" x1="0" y1="0" x2="0" y2="300" />     <rect x="0" y="0" width="200" height="100" fill="red" opacity="0.9" transform="skewY(30)"></rect>  </svg><h1>transform: matrix(1, tan(a'), 0, 1, 0, 0)</h1>  <svg x="0px" y="0px" width="600px" height="300px">    <line label="axisX" fill="none" stroke="black" stroke-width="10" x1="0" y1="0" x2="600" y2="0" />     <line label="axisY" fill="none" stroke="black" stroke-width="10" x1="0" y1="0" x2="0" y2="300" />      <rect x="0" y="0" width="200" height="100" fill="red" opacity="0.9" transform="matrix(1,0.577350,0,1,0,0)"></rect>  </svg>
</div>

综上,就是 transform 全部函数的计算方式了。

或者也可以认为是它的矩阵运算描述。

当然,代码实现的时候可能会为了减少不必要的矩阵运算,从而做了最优化处理。但是理解它的运算原理,清楚底层的计算逻辑,却是十分有益的。

【此文原创,欢迎转发,禁止搬运】

前端 · 深入理解 transform 函数的计算原理 ①相关推荐

  1. 前端 · 深入理解 transform 函数的计算原理 ②

    前排提示:本篇为该系列第二篇,内容相对于第一篇来说比较简单,各位可当休闲读物来看. <前端 · 深入理解 transform 函数的计算原理 ①> 接上回书讲到,我们知道了 transfo ...

  2. 前端移动端的rem适配计算原理

    rem是什么? rem(font size of the root element)是指相对于根元素的字体大小的单位.简单的说它就是一个相对单位.看到rem大家一定会想起em单位,em(font si ...

  3. python agg函数_个人对Pandas中agg、apply和transform函数的理解

    个人对Pandas中agg.apply和transform函数的理解 学习<利用Python进行数据分析>一书,关于pandas的这三个函数,个人理解如下. agg agg方法可以被gro ...

  4. 通过简单的计算理解sumproduct函数(图文)

    目录 前言 1.功能 2.验证过程 2.1 数值型数据 2.2 非数值型数据 2.2.1 布尔类型进行四则运算 2.2.2 文本型四则运算 3. 注意事项 前言 本文主要从函数的计算过程来讲解,从自己 ...

  5. 前端面试 vue生命周期钩子是如何实现的?理解vue中模板编译原理?

    生命周期钩子在内部会被vue维护成一个数组(vue 内部有一个方法mergeOption)和全局的生命周期合并最终转换成数组,当执行到具体流程时会执行钩子(发布订阅模式),callHook来实现调用. ...

  6. crc16的c语言函数 计算ccitt_CCITT CRC-16计算原理与实现

    计算原理与实现 CRC 的全称为 Cyclic Redundancy Check ,中文名称为循环冗余校验.它是一类 重要的线性分组码, 编码和解码方法简单, 检错和纠错能力强, 在通信领域广泛 地用 ...

  7. crc16的c语言函数 计算ccitt_CCITT CRC-16计算原理与实现CRC-ITU

    CCITT CRC-16 计算原理与实现 时间: 201 1 -08-28 22:37 :20 来源: 作者: CRC 的全称为 Cy clic Redundancy Check ,中文名称为循环冗余 ...

  8. 文本相似度php,分析php计算文本字符串相似度函数similar_text()的原理

    PHP有个计算两个文本字符串相似度的函数similar_text(),可以得出一个百分比来表示两个字符串的相似程度.效果如下: similar_text('aaaa', 'aaaa', $percen ...

  9. Direct3D Draw函数 异步调用原理解析

    概述 在D3D10中,一个基本的渲染流程可分为以下步骤: 清理帧缓存: 执行若干次的绘制: 通过Device API创建所需Buffer: 通过Map/Unmap填充数据到Buffer中: 将Buff ...

最新文章

  1. HTML 5 应用程序缓存
  2. 使用data uri将图片内嵌到html中
  3. Mysql报错Fatal error: Can#39;t open and lock privilege tables: Table #39;mysql.host#39; doesn#39;t...
  4. eclipse创建java web项目
  5. 2020蓝桥杯省赛---java---B---8(走方格)
  6. spring与junit整合测试
  7. 客服中心智能化技术和应用研究报告(2021年)
  8. Android 系统(181 )----Android中各类.mk文件的编写
  9. 【转】状态压缩动态规划
  10. P4145 上帝造题的七分钟2 / BZOJ3211花神游历各国
  11. ECSHOP始终显示全部分类方法
  12. [SOA] Mule ESB 3.x 入门(二)—— 配置(spring, properties, log4j)
  13. C/C++[codeup 2044]神奇的口袋
  14. 树链剖分解析---WYD
  15. FIT2CLOUD飞致云成为Kubernetes认证服务提供商(KCSP)
  16. 第一代计算机的拼音,计算机系列拼音
  17. 使用scrapy抓取堆糖图片
  18. 猎人打猎(二)----利用继承
  19. ResultSet(结果集)、Statement
  20. 计算机任务无法结束,简单几步解决win7任务管理器无法结束进程的问题

热门文章

  1. 222222222222
  2. 银河麒麟桌面操作系统V10创建热点
  3. c++二叉树打印(只为美观)
  4. final 修饰的成员变量必须手动初始化
  5. yocto(二)——bitbake工作流程
  6. [开源精品] C#.NET im 聊天通讯架构设计 -- FreeIM 支持集群、职责分明、高性能
  7. vscode css智能补全_在 Webstorm 伤透我的心后,我决定尝试 VS Code
  8. Hinton:胶囊网络的专利是我的了
  9. OpenWrt下使用iperf测试多跳网络性能
  10. 二.网络布线与进制转换——详解