文章以二维变换为例说明,三维变换采用类似的操作理解即可。
变换的座标系统
默认的变换座标系统,变换原点为元素中心,x轴与y轴与web中定义的一致。其中,变换原点可以用transform-origin属性来设置。transform是以该座标系统为参考,对变换中图形上每个点的座标进行更新,来得到变换后的图形。
线性变换
线性变换需要满足两个条件:
- 不改变座标原点
- 每个点的座标在更新时,采用线性函数来更新。用数学中的线性方程表达如下{x′=y′=ax+cybx+dy
用矩阵的形式表示为[x′y′]=[abcd][xy]
CSS Transform中,除了translate变换外,其他都属于线性变换。因此,除了translate外,其他变换可以用2*2的矩阵描述如下[abcd],而translate变换需要额外描述,因此CSS Transform的矩阵描述如下⎣⎡ab0cd0ef1⎦⎤其中,[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矩阵为⎣⎡100010001⎦⎤,为一个3*3的单位矩阵。因此,所有点的座标没有任何变化。
translate变换
translate变换只是给e和f赋值,其中translateX()给e赋值,translateY()给f赋值,此时matrix矩阵为⎣⎡100010ef1⎦⎤
变换的过程表示为⎣⎡x′y′1⎦⎤=⎣⎡100010ef1⎦⎤⎣⎡xy1⎦⎤线性方程为
{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矩阵,指的是⎣⎡cosαsinα0−sinαcosα0001⎦⎤变换的过程表示为⎣⎡x′y′1⎦⎤=⎣⎡cosαsinα0−sinαcosα0001⎦⎤⎣⎡xy1⎦⎤线性方程为{x′=y′=xcosα−ysinαxsinα+ycosα
可以看出,旋转变换是对x和y座标进行三角函数运算
scale变换
scale()变换是指对图形的缩放,按照某个比例放大或缩小图形。缩放是对x轴和y轴座标刻度的更改,因此对应于matrix矩阵,指的是⎣⎡a000d0001⎦⎤a表示x轴的缩放比例,d表示y轴的缩放比例。变换的过程为⎣⎡x′y′1⎦⎤=⎣⎡a000d0001⎦⎤⎣⎡xy1⎦⎤线性方程为{x′=y′=axdy可以看出,的确是将x座标乘以a,将y座标乘以d。
skew变换
skew()变换指的是将图形的水平线或垂直线做一定程度的倾斜,即看起来变歪了。由于倾斜涉及到角度,因此matrix矩阵为⎣⎡1tanθy0tanθx10001⎦⎤其中,θx和θy分别为skewX()和skewY()的参数,θx表示垂直线倾斜角(正数表示向x轴正半轴倾斜),θy表示水平线倾斜角(正数表示向y轴正半轴倾斜)。这一点从线性方程中可以看出。变换过程为⎣⎡x′y′1⎦⎤=⎣⎡1tanθy0tanθx10001⎦⎤⎣⎡xy1⎦⎤线性方程为{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α0001⎦⎤⎣⎡100010ef1⎦⎤⎣⎡xy1⎦⎤把中间的matrix矩阵计算一下⎣⎡cosαsinα0−sinαcosα0001⎦⎤⎣⎡100010ef1⎦⎤=⎣⎡cosαsinα0−sinαcosα0ecosα−fsinαesinα+fcosα1⎦⎤线性方程为{x′=y′=xcosα−ysinα+ecosα−fsinαxsinα+ycosα+esinα+fcosα
理解方式1:座标原点不变
从这里可以看出,变换后的座标相当于对原座标做rotate变换,加上对translate增加的座标做的rotate变换。如果我们写成{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变换会更改座标原点的方式,座标原点变为[ecosα−fsinαesinα+fcosα],此时图形的座标相对于新的座标系,则线性方程变为{x′=y′=xcosα−ysinαxsinα+ycosα
此时的操作相当于,对座标原点进行平移translate(e, f),然后再rotate(α),最后再对图形在新座标原点上做rotate(α)。因此,translate变换如果在其他变换的右边,则其他变换要加在translate变换上再对座标原点做变换。其实,用座标原点跟随translate改变的理解方式比较复杂。
因此,复合变换中,translate变换如果出现在其他变换的右边,那么该复合变换不是变换之间简单的线性叠加,而是translate变换会与其他变换进行线性叠加(应用到座标原点,从而得到新的座标原点),再将其他变换线性叠加(直接给图形在新座标系应用这些变换)
transform: translate(e, f) rotate(α)
作为对比,来看一下顺序相反的情况。计算得到最终的matrix矩阵为⎣⎡cosαsinα0−sinαcosα0ef1⎦⎤线性方程为{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矩阵为[1tanθytanθx1][cosαsinα−sinαcosα]=[cosα+sinαtanθxsinα+cosαtanθy−sinα+cosαtanθxcosα−sinαtanθy]
线性方程为{x′=y′=(xcosα−ysinα)+(xsinα+ycosα)tanθx(xsinα+ycosα)+(xcosα−ysinα)tanθy可以发现,正好是rotate后的座标再进行skew()的过程。如果我们定义xr和yr为rotate后的座标,可以清晰地发现这一点。rotate后的座标如下{xr=yr=xcosα−ysinαxsinα+ycosα再进行skew的座标如下{x′=y′=xr+yrtanθyyr+xrtanθx
transform: rotate(α) skew(θx, θy)对应的matrix矩阵为[cosαsinα−sinαcosα][1tanθytanθx1]=[cosα−sinαtanθysinα+cosαtanθy−sinα+cosαtanθxcosα+sinαtanθx]线性方程为{x′=yr=xr+yrtanθxyr+xrtanθy
这说明,线性变换之间的顺序不会引入对座标原点的变换,只要按照顺序依次应用变换即可。