文章以二维变换为例说明,三维变换采用类似的操作理解即可。
变换的坐标系统
默认的变换坐标系统,变换原点为元素中心,x轴与y轴与web中定义的一致。其中,变换原点可以用transform-origin属性来设置。transform是以该坐标系统为参考,对变换中图形上每个点的坐标进行更新,来得到变换后的图形。
线性变换
线性变换需要满足两个条件:
- 不改变坐标原点
- 每个点的坐标在更新时,采用线性函数来更新。用数学中的线性方程表达如下
{
x
′
=
a
x
+
c
y
y
′
=
b
x
+
d
y
\begin{cases}x\prime =&ax+cy\\ y\prime =&bx+dy\end{cases}
{x′=y′=ax+cybx+dy
用矩阵的形式表示为 [ x ′ y ′ ] = [ a c b d ] [ x y ] \begin{bmatrix}x\prime \\ y\prime \end{bmatrix} =\begin{bmatrix}a&c\\ b&d\end{bmatrix} \begin{bmatrix}x\\ y\end{bmatrix} [x′y′]=[abcd][xy]
CSS Transform中,除了translate变换外,其他都属于线性变换。因此,除了translate外,其他变换可以用2*2的矩阵描述如下 [ a c b d ] \begin{bmatrix}a&c\\ b&d\end{bmatrix} [abcd],而translate变换需要额外描述,因此CSS Transform的矩阵描述如下 [ a c e b d f 0 0 1 ] \begin{bmatrix}a&c&e\\ b&d&f\\ 0&0&1\end{bmatrix} ⎣⎡ab0cd0ef1⎦⎤其中, [ a c e b d f ] \begin{bmatrix}a&c&e\\ b&d&f\end{bmatrix} [abcdef]其实足够描述二维变换了,最后一行是为了矩阵运算而补充的。e和f就是为了translate而增加的额外描述,e用来描述translateX,f用来描述translateY。
matrix()函数
CSS Transform中的所有变换,实际上都是matrix()函数的封装,为了便于记忆和使用。在二维变换中,matrix()函数接收6个参数为matrix(a, b, c, d, e, f),即为上文中的矩阵元素。matrix()函数本质上就是上文中的变换矩阵,对于不同的变换,6个参数各不相同。
下列变换中,假设没有变换时,图形上点的坐标为(x, y),变换后为(x’, y’)。为了方便运算,点的坐标额外增加一行为1;
没有变换
matrix矩阵为 [ 1 0 0 0 1 0 0 0 1 ] \begin{bmatrix}1&0&0\\ 0&1&0\\ 0&0&1\end{bmatrix} ⎣⎡100010001⎦⎤,为一个3*3的单位矩阵。因此,所有点的坐标没有任何变化。
translate变换
translate变换只是给e和f赋值,其中translateX()给e赋值,translateY()给f赋值,此时matrix矩阵为
[
1
0
e
0
1
f
0
0
1
]
\begin{bmatrix}1&0&e\\ 0&1&f\\ 0&0&1\end{bmatrix}
⎣⎡100010ef1⎦⎤
变换的过程表示为
[
x
′
y
′
1
]
=
[
1
0
e
0
1
f
0
0
1
]
[
x
y
1
]
\begin{bmatrix}x\prime \\ y\prime \\ 1\end{bmatrix} =\begin{bmatrix}1&0&e\\ 0&1&f\\ 0&0&1\end{bmatrix} \begin{bmatrix}x\\ y\\ 1\end{bmatrix}
⎣⎡x′y′1⎦⎤=⎣⎡100010ef1⎦⎤⎣⎡xy1⎦⎤线性方程为
{
x
′
=
x
+
e
y
′
=
y
+
f
\begin{cases}x\prime =&x+e\\ y\prime =&y+f\end{cases}
{x′=y′=x+ey+f
可以看出,translate变换相当于在原来坐标系统下,每个点的x坐标增加e,y坐标增加f,得到了新的坐标。
改变坐标原点的理解:也可以这样理解,让e和f都等于0,此时每个点的坐标都不变,而坐标系统的原点从(0, 0)移动到了(e, f)。从这个角度来看,translate改变了坐标原点的位置。也就是说,原来的图形坐标都不用变,只要把坐标原点移动到(e, f)的位置即可。注意:(e, f)这个位置坐标是以原坐标系统为参考点的。用这种方式理解的前提是,原来的坐标系统用来定位新的坐标系统,新的坐标系统用来定位图形的坐标。
rotate变换
rotate()变换是指围绕原点,旋转某个角度α,正数表示顺时针旋转,负数表示逆时针旋转。对应于matrix矩阵,指的是
[
c
o
s
α
−
s
i
n
α
0
s
i
n
α
c
o
s
α
0
0
0
1
]
\begin{bmatrix}cos\alpha &-sin\alpha &0\\ sin\alpha &cos\alpha &0\\ 0&0&1\end{bmatrix}
⎣⎡cosαsinα0−sinαcosα0001⎦⎤变换的过程表示为
[
x
′
y
′
1
]
=
[
c
o
s
α
−
s
i
n
α
0
s
i
n
α
c
o
s
α
0
0
0
1
]
[
x
y
1
]
\begin{bmatrix}x\prime \\ y\prime \\ 1\end{bmatrix} =\begin{bmatrix}cos\alpha &-sin\alpha &0\\ sin\alpha &cos\alpha &0\\ 0&0&1\end{bmatrix} \begin{bmatrix}x\\ y\\ 1\end{bmatrix}
⎣⎡x′y′1⎦⎤=⎣⎡cosαsinα0−sinαcosα0001⎦⎤⎣⎡xy1⎦⎤线性方程为
{
x
′
=
x
cos
α
−
y
sin
α
y
′
=
x
sin
α
+
y
cos
α
\begin{cases}x\prime =&x\cos \alpha -y\sin \alpha \\ y\prime =&x\sin \alpha +y\cos \alpha \end{cases}
{x′=y′=xcosα−ysinαxsinα+ycosα
可以看出,旋转变换是对x和y坐标进行三角函数运算
scale变换
scale()变换是指对图形的缩放,按照某个比例放大或缩小图形。缩放是对x轴和y轴坐标刻度的更改,因此对应于matrix矩阵,指的是 [ a 0 0 0 d 0 0 0 1 ] \begin{bmatrix}a&0&0\\ 0&d&0\\ 0&0&1\end{bmatrix} ⎣⎡a000d0001⎦⎤a表示x轴的缩放比例,d表示y轴的缩放比例。变换的过程为 [ x ′ y ′ 1 ] = [ a 0 0 0 d 0 0 0 1 ] [ x y 1 ] \begin{bmatrix}x\prime \\ y\prime \\ 1\end{bmatrix} =\begin{bmatrix}a&0&0\\ 0&d&0\\ 0&0&1\end{bmatrix} \begin{bmatrix}x\\ y\\ 1\end{bmatrix} ⎣⎡x′y′1⎦⎤=⎣⎡a000d0001⎦⎤⎣⎡xy1⎦⎤线性方程为 { x ′ = a x y ′ = d y \begin{cases}x\prime =&ax\\ y\prime =&dy\end{cases} {x′=y′=axdy可以看出,的确是将x坐标乘以a,将y坐标乘以d。
skew变换
skew()变换指的是将图形的水平线或垂直线做一定程度的倾斜,即看起来变歪了。由于倾斜涉及到角度,因此matrix矩阵为 [ 1 tan θ x 0 tan θ y 1 0 0 0 1 ] \begin{bmatrix}1&\tan \theta_{x} &0\\ \tan \theta_{y} &1&0\\ 0&0&1\end{bmatrix} ⎣⎡1tanθy0tanθx10001⎦⎤其中,θx和θy分别为skewX()和skewY()的参数,θx表示垂直线倾斜角(正数表示向x轴正半轴倾斜),θy表示水平线倾斜角(正数表示向y轴正半轴倾斜)。这一点从线性方程中可以看出。变换过程为 [ x ′ y ′ 1 ] = [ 1 tan θ x 0 tan θ y 1 0 0 0 1 ] [ x y 1 ] \begin{bmatrix}x\prime \\ y\prime \\ 1\end{bmatrix} =\begin{bmatrix}1&\tan \theta_{x} &0\\ \tan \theta_{y} &1&0\\ 0&0&1\end{bmatrix} \begin{bmatrix}x\\ y\\ 1\end{bmatrix} ⎣⎡x′y′1⎦⎤=⎣⎡1tanθy0tanθx10001⎦⎤⎣⎡xy1⎦⎤线性方程为 { x ′ = x + y tan θ x y ′ = y + x tan θ y \begin{cases}x\prime =&x+y\tan \theta_{x} \\ y\prime =&y+x\tan \theta_{y} \end{cases} {x′=y′=x+ytanθxy+xtanθy从中可以看出,x和y坐标都是在原来基础上,增加了一部分由角度决定的量,结果就是变歪了。
复合变换
这里的复合变换,指的是一个transform属性中,包含以上几个变换,由以上几个变换按顺序组成。这里的顺序非常重要,因为顺序不同,最后得到的matrix矩阵会不同。尤其在包含translate变换的情况下,因为translate的非线性,导致的变换差异很大。
复合变换的规则是,transform属性中,按从左向右的顺序,将代表各个变换的matrix矩阵依次相乘,得到最终的matrix矩阵。由于矩阵乘法不满足交换律,因此这里的顺序很重要。另外,translate如果放在其他变换的右边,其他变换就会连translate变换一起变换。听起来很乱吧,下面用matrix矩阵相乘的法则来演示。
transform: rotate(α) translate(e, f)
以上面的变换为例,这意味着先进行translate变换,再进行rotate变换。变换过程为 [ x ′ y ′ 1 ] = [ cos α − sin α 0 sin α cos α 0 0 0 1 ] [ 1 0 e 0 1 f 0 0 1 ] [ x y 1 ] \begin{bmatrix}x\prime \\ y\prime \\ 1\end{bmatrix} =\begin{bmatrix}\cos \alpha &-\sin \alpha &0\\ \sin \alpha &\cos \alpha &0\\ 0&0&1\end{bmatrix} \begin{bmatrix}1&0&e\\ 0&1&f\\ 0&0&1\end{bmatrix} \begin{bmatrix}x\\ y\\ 1\end{bmatrix} ⎣⎡x′y′1⎦⎤=⎣⎡cosαsinα0−sinαcosα0001⎦⎤⎣⎡100010ef1⎦⎤⎣⎡xy1⎦⎤把中间的matrix矩阵计算一下 [ cos α − sin α 0 sin α cos α 0 0 0 1 ] [ 1 0 e 0 1 f 0 0 1 ] = [ cos α − sin α e cos α − f sin α sin α cos α e sin α + f cos α 0 0 1 ] \begin{bmatrix}\cos \alpha &-\sin \alpha &0\\ \sin \alpha &\cos \alpha &0\\ 0&0&1\end{bmatrix} \begin{bmatrix}1&0&e\\ 0&1&f\\ 0&0&1\end{bmatrix} =\begin{bmatrix}\cos \alpha &-\sin \alpha &e\cos \alpha -f\sin \alpha \\ \sin \alpha &\cos \alpha &e\sin \alpha +f\cos \alpha \\ 0&0&1\end{bmatrix} ⎣⎡cosαsinα0−sinαcosα0001⎦⎤⎣⎡100010ef1⎦⎤=⎣⎡cosαsinα0−sinαcosα0ecosα−fsinαesinα+fcosα1⎦⎤线性方程为 { x ′ = x cos α − y sin α + e cos α − f sin α y ′ = x sin α + y cos α + e sin α + f cos α \begin{cases}x\prime =&x\cos \alpha -y\sin \alpha +e\cos \alpha -f\sin \alpha \\ y\prime =&x\sin \alpha +y\cos \alpha +e\sin \alpha +f\cos \alpha \end{cases} {x′=y′=xcosα−ysinα+ecosα−fsinαxsinα+ycosα+esinα+fcosα
理解方式1:坐标原点不变
从这里可以看出,变换后的坐标相当于对原坐标做rotate变换,加上对translate增加的坐标做的rotate变换。如果我们写成 { x ′ = ( x + e ) cos α − ( y + f ) sin α y ′ = ( x + e ) sin α + ( y + f ) cos α \begin{cases}x\prime =&\left( x+e\right) \cos \alpha -\left( y+f\right) \sin \alpha \\ y\prime =&\left( x+e\right) \sin \alpha +\left( y+f\right) \cos \alpha \end{cases} {x′=y′=(x+e)cosα−(y+f)sinα(x+e)sinα+(y+f)cosα可以看出,先做translate变换,然后将变换后的坐标整体再做rotate变换。我们的坐标系统没有变,坐标原点仍然是(0, 0),将translate所做变换后的坐标,再一起做rotate变换。对应到图形上,就是让图形先做translate变换,然后变换后的图形再做rotate变换(坐标原点为(0, 0) )。
理解方式2:坐标原点随translate改变
根据translate变换会更改坐标原点的方式,坐标原点变为
[
e
cos
α
−
f
sin
α
e
sin
α
+
f
cos
α
]
\begin{bmatrix}e\cos \alpha -f\sin \alpha \\ e\sin \alpha +f\cos \alpha \end{bmatrix}
[ecosα−fsinαesinα+fcosα],此时图形的坐标相对于新的坐标系,则线性方程变为
{
x
′
=
x
cos
α
−
y
sin
α
y
′
=
x
sin
α
+
y
cos
α
\begin{cases}x\prime =&x\cos \alpha -y\sin \alpha \\ y\prime =&x\sin \alpha +y\cos \alpha \end{cases}
{x′=y′=xcosα−ysinαxsinα+ycosα
此时的操作相当于,对坐标原点进行平移translate(e, f),然后再rotate(α),最后再对图形在新坐标原点上做rotate(α)。因此,translate变换如果在其他变换的右边,则其他变换要加在translate变换上再对坐标原点做变换。其实,用坐标原点跟随translate改变的理解方式比较复杂。
因此,复合变换中,translate变换如果出现在其他变换的右边,那么该复合变换不是变换之间简单的线性叠加,而是translate变换会与其他变换进行线性叠加(应用到坐标原点,从而得到新的坐标原点),再将其他变换线性叠加(直接给图形在新坐标系应用这些变换)
transform: translate(e, f) rotate(α)
作为对比,来看一下顺序相反的情况。计算得到最终的matrix矩阵为 [ cos α − sin α e sin α cos α f 0 0 1 ] \begin{bmatrix}\cos \alpha &-\sin \alpha &e\\ \sin \alpha &\cos \alpha &f\\ 0&0&1\end{bmatrix} ⎣⎡cosαsinα0−sinαcosα0ef1⎦⎤线性方程为 { x ′ = x cos α − y sin α + e y ′ = x sin α + y cos α + f \begin{cases}x\prime =&x\cos \alpha -y\sin \alpha +e\\ y\prime =&x\sin \alpha +y\cos \alpha +f\end{cases} {x′=y′=xcosα−ysinα+exsinα+ycosα+f可以看出,先rotate操作,再直接加上translate(e, f)坐标增量即可,不会出现上方的对translate()坐标增量也要rotate的操作。
transform: skew(θx, θy) rotate(α)与transform: rotate(α) skew(θx, θy)的对比
由于不涉及translate变换,所以我们用2*2的矩阵表示就可以了。
transform: skew(θx, θy) rotate(α)对应的matrix矩阵为
[
1
tan
θ
x
tan
θ
y
1
]
[
cos
α
−
sin
α
sin
α
cos
α
]
=
[
cos
α
+
sin
α
tan
θ
x
−
sin
α
+
cos
α
tan
θ
x
sin
α
+
cos
α
tan
θ
y
cos
α
−
sin
α
tan
θ
y
]
\begin{bmatrix}1&\tan \theta_{x} \\ \tan \theta_{y} &1\end{bmatrix} \begin{bmatrix}\cos \alpha &-\sin \alpha \\ \sin \alpha &\cos \alpha \end{bmatrix} =\begin{bmatrix}\cos \alpha +\sin \alpha \tan \theta_{x} &-\sin \alpha +\cos \alpha \tan \theta_{x} \\ \sin \alpha +\cos \alpha \tan \theta_{y} &\cos \alpha -\sin \alpha \tan \theta_{y} \end{bmatrix}
[1tanθytanθx1][cosαsinα−sinαcosα]=[cosα+sinαtanθxsinα+cosαtanθy−sinα+cosαtanθxcosα−sinαtanθy]
线性方程为
{
x
′
=
(
x
cos
α
−
y
sin
α
)
+
(
x
sin
α
+
y
cos
α
)
tan
θ
x
y
′
=
(
x
sin
α
+
y
cos
α
)
+
(
x
cos
α
−
y
sin
α
)
tan
θ
y
\begin{cases}x\prime =&\left( x\cos \alpha -y\sin \alpha \right) +\left( x\sin \alpha +y\cos \alpha \right) \tan \theta_{x} \\ y\prime =&\left( x\sin \alpha +y\cos \alpha \right) +\left( x\cos \alpha -y\sin \alpha \right) \tan \theta_{y} \end{cases}
{x′=y′=(xcosα−ysinα)+(xsinα+ycosα)tanθx(xsinα+ycosα)+(xcosα−ysinα)tanθy可以发现,正好是rotate后的坐标再进行skew()的过程。如果我们定义xr和yr为rotate后的坐标,可以清晰地发现这一点。rotate后的坐标如下
{
x
r
=
x
cos
α
−
y
sin
α
y
r
=
x
sin
α
+
y
cos
α
\begin{cases}x_{r}=&x\cos \alpha -y\sin \alpha \\ y_{r}=&x\sin \alpha +y\cos \alpha \end{cases}
{xr=yr=xcosα−ysinαxsinα+ycosα再进行skew的坐标如下
{
x
′
=
x
r
+
y
r
tan
θ
y
y
′
=
y
r
+
x
r
tan
θ
x
\begin{cases}x\prime =&x_{r}+y_{r}\tan \theta_{y} \\ y\prime =&y_{r}+x_{r}\tan \theta x\end{cases}
{x′=y′=xr+yrtanθyyr+xrtanθx
transform: rotate(α) skew(θx, θy)对应的matrix矩阵为
[
cos
α
−
sin
α
sin
α
cos
α
]
[
1
tan
θ
x
tan
θ
y
1
]
=
[
cos
α
−
sin
α
tan
θ
y
−
sin
α
+
cos
α
tan
θ
x
sin
α
+
cos
α
tan
θ
y
cos
α
+
sin
α
tan
θ
x
]
\begin{bmatrix}\cos \alpha &-\sin \alpha \\ \sin \alpha &\cos \alpha \end{bmatrix} \begin{bmatrix}1&\tan \theta_{x} \\ \tan \theta_{y} &1\end{bmatrix} =\begin{bmatrix}\cos \alpha -\sin \alpha \tan \theta_{y} &-\sin \alpha +\cos \alpha \tan \theta_{x} \\ \sin \alpha +\cos \alpha \tan \theta_{y} &\cos \alpha +\sin \alpha \tan \theta_{x} \end{bmatrix}
[cosαsinα−sinαcosα][1tanθytanθx1]=[cosα−sinαtanθysinα+cosαtanθy−sinα+cosαtanθxcosα+sinαtanθx]线性方程为
{
x
′
=
x
r
+
y
r
tan
θ
x
y
r
=
y
r
+
x
r
tan
θ
y
\begin{cases}x\prime =&x_{r}+y_{r}\tan \theta_{x} \\ y_{r}=&y_{r}+x_{r}\tan \theta y\end{cases}
{x′=yr=xr+yrtanθxyr+xrtanθy