CSS Transforms能够对一个元素进行二维平面或三维空间的变换,如translate, rotate, scale和skew等变换。css
下面是对W3C官网CSS Transforms模块的部分摘译,为了理解的连贯性,调整了W3C规范中相关章节的顺序。css3
用户浏览器(UAs)可能不总能渲染出三维变换,那么它们就只能支持该规范的一个二维子集。在这种状况下,三维变换和transform-style、perspective、perspective-origin以及backface-visibility属性将不被支持。三维相关的变换渲染也不会起做用。git
对于二维变换状况,矩阵分解采用Graphics Gems II(Jim Arvo著)书中unmatrix算法的二维简化版本。下面是一个二维的3x3变换矩阵,其中6个参数a~f,分别对应二维变换函数matrix(a, b, c, d, e, f)的6个参数。github
![]()
二维变换的3x3矩阵 图1 二维变换3x3矩阵算法
开发者能够很简单的为不支持三维变换的浏览器提供备选变换方案。下面的例子中,transform属性有两次赋值定义。第一次定义包括了两个二维变换函数,第二次定义包括一个二维变换和一个三维变换。浏览器
div { transform: scale(2) rotate(45deg); transform: scale(2) rotate3d(0, 0, 1, 45deg); }复制代码
当浏览器支持三维变换时,第二次属性定义将覆盖第一次的定义,当不支持三维变换时,第二次定义将会失效,浏览器会使用第一种定义。函数
当为元素的transform属性指定了一个非none属性值时,就会在该元素上创建了一个本地坐标系(local coordinate system)。从元素最初始的渲染(未指定transform属性值)到本地坐标系的映射由该元素的变换矩阵(transformation matrix)给定。变换是可累积的,也就是说,元素是在它们祖先的坐标系中创建本身的本地坐标系。从用户的角度来看,就是一个元素会累积应用全部它祖先‘transform’属性设置的变换,以及自身"transform"属性设置的变换,规范中将这些变换的累积称为元素当前变换矩阵(current transformation matrix, CTM)。ui
坐标空间是一个有两个轴的坐标系:X轴是水平向右增长,Y轴是垂直向下增长。三维变换函数将这个坐标空间扩展到三维,增长了垂直于屏幕平面一个Z轴,而且指向观察者。spa
![]()
初始坐标空间示例 图2 初始坐标空间示例
变换矩阵是基于'transform'和'transform-origin'属性,按照如下步骤计算而来:
注意,变换只是影响了元素的显示,不影响元素自身CSS的布局。意味着变化不会影响getClientRects()及getBoundingClientRect()的值。对于基于CSS盒模型布局定位的元素,若是该元素的transform属性为非none,则该元素将成为其全部fixed定位的后代元素的包含块(Containing Block)。
transform中一些变换函数的效果能够经过更具体的变换函数来实现,好比translate的一些操做能够用translateX来实现。此时称translate为元变换,translateX为派生变换。
下面列出了全部二维和三维的元变换以及相应的派生变换。
元变换 | 派生变换 |
---|---|
translate() | translateX(), translateY(), translate() |
rotate()带三个参数 | rotate()带一个或三个参数 |
scale() | scaleX(), scaleY(),scale() |
元变换 | 派生变换 |
---|---|
translate3d() | translateX(), translateY(), translateZ(), translate() |
scale3d() | scaleX(), scaleY(), scaleZ(), scale() |
rotate3d() | rotate(), roateX(), rotateY(), rotateZ() |
对于同时具备二维和三维元变换的派生变换,具体是使用二维元变换或三维的元变换,是由上下文环境来决定。
两个具备相同数量参数的变换函数,会直接进行数值的插值计算,而不须要转换为相同的元变换。插值计算的结果便是带有相同参数的相同变换。对于rotate3d(), matrix(), matrix3d(), perspective()有特殊的插值计算规则。
例如,对于变换函数translate(0)和translate(100px),就是两个具备相同数量参数的相同变换,因此它们会直接进行数值上的插值计算。可是对于变换函数translateX(100px)和translate(100px, 0),两个变换既不是相同的变换,使用的参数数量也不一样,因此它们就须要先转换为元变换函数,而后才能进行数值上的插值计算。
两个不一样的变换,但都是从相同的元变换派生出来的(即相同的派生变换),或者相同的变换,但使用了不一样数量的参数,此时两个变换能够进行数值插值计算。须要先将两种变换转换为相同的元变换,而后才能进行数值插值计算。插值计算的结果至关于使用了相同数量参数的相同元变换。
下面的例子,当div发生鼠标hover事件时,会发生从translateX(100px)到translateY(100px)的3秒过渡变换。此时两个变换都是从相同的元变换translate()派生的,因此须要先将两个变换转换为translate()元变换,而后才能进行数值插值计算。
div { transform: translateX(100px); } div:hover { transform: translateY(100px); transition: transform 3s; }复制代码
当发生3秒的transition时,translateX(100px)将会转化为translate(100px, 0),translateY(100px)会转化为translate(0, 100px),而后两个变换才能进行数值的插值计算。
若是两个变换均可以从同一个二维元变换派生,则都会转换为二维元变换。若是其中一个是或者都是三维变换,则会都转换为三维元变换。
下面的例子中,一个二维变换函数通过3秒过渡变换到三维变换函数。两个变换函数的公共元变换为translate3d()。
div { transform: translateX(100px); } div:hover { transform: translateZ(100px); transition: transform 3s; }复制代码
当发生3s的transition时,translateX(100px)会转化为translate3d(100px, 0, 0),translateZ(100px)会转化为translate3d(0, 0, 100px),而后两个变换才能进行数值的插值计算。
对于matrix(), matrix3d(), perspective()三种变换将会首先被转化为4x4的矩阵,而后进行矩阵的插值计算。
对于rotate3d()的插值计算,首先会获得两个变换的单位方向向量,若是相等,则能够直接进行数值的插值计算;不然,就须要先将两种变换转化为4x4的矩阵,而后对矩阵进行插值计算。
当变换函数之间发生过渡时(好比对transforms施加transition属性),就须要对变换函数进行插值计算。从一个初始的变换(from-transform)到一个结束的变换(to-transform),如何进行插值计算须要遵循下面的规则。
此时没有必要进行计算,保持原值。
值为none的那个将被替换为恒等变化(Identity Transform functions),而后继续按照下面的规则进行插值计算。
恒等变换(Identity Transform functions) 就是标准里给出的一系列特殊的变换函数,相似线性代数里面的单位矩阵(Identity Matrix),不管怎么施加多少次变换,都不会改变原有的变换,标准里面给出的恒等变换有translate(0)、translate3d(0, 0, 0)、translateX(0)、translateY(0)、translateZ(0)、scale(1)、scaleX(1)、scaleY(1)、scaleZ(1)、rotate(0)、rotate3d(1, 1, 1, 0)、rotateX(0)、rotateY(0)、rotateZ(0)、skew(0, 0)、skewX(0)、skewY(0)、matrix(1, 0, 0, 1, 0, 0)和matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1)。一种特殊的状况是透视(perspective): perspective(infinity),此时M34的值变得无穷小,所以假定它等于单位矩阵。
例如,from-transform为scale(2),to-transform为none, 则none将会被替换为scale(1),而后继续按照下面的规则进行插值。相似的,若是from-transform为none,to-transform为scale(2) rotate(50deg),则from-transform会被替换为scale(1) rotate(0)。
按照元变换函数和派生变换函数的插值里面的步骤,对from-transform和to-transform对应的变换进行插值。计算出的结果做为最终的变换。
例如,from-transform为scale(1) translate(0),to-transform为translate(100px) scale(2),虽然都是用了scale和translate变换,可是对应的变换既不相同也不是从相同的元变换派生出来的,此时就不能按照本条规则来进行插值计算。
from-transform和to-transform中使用的变换都会被转化为4x4的矩阵,并进行相应的矩阵插值计算。若是from-transform和to-transform对应的变换矩阵均可以表示成3x2矩阵或者matrix3d矩阵,则元素就按照相应插值计算的矩阵进行变换渲染。在某些特殊的状况下,变化矩阵多是一个奇异或不可逆矩阵,则元素将不会被渲染。
当对两个矩阵进行插值时,首先将矩阵分解为一系列变换操做的值,好比对应的translation、rotation、scale和skew的变换矩阵,而后对各个变换操做对应的矩阵进行数值插值计算,最后将各个变换矩阵从新组合为原始矩阵。
下面的例子中,元素初始变换为rotate(45deg),当发生hover时,将会在X轴和Y轴移动100像素,并旋转1170度。若是设计人员给出下面的写法,极可能是指望看到元素会顺时针旋转3.5圈(1170度)。
<style> div { transform: rotate(45deg); } div:hover { transform: translate(100px, 100px) rotate(1215deg); transition: transform 3s; } </style> <div></div>复制代码
初始变换‘rotate(45deg)’和目标变换'translate(100px, 100px) rotate(1125deg)'彻底不一样,按照*变换的插值 最后一条规则,两个变换都须要进行矩阵的插值计算。但须要注意,将变换转化为矩阵的过程当中,旋转3圈的信息将会丢失掉,因此最终看到的效果只顺时针旋转了半圈(90度)。
为了达到指望的效果,只须要更新上述变换的写法,使先后两个变换知足变换的插值* 的第三条规则。初始变换改成‘translate(0, 0) rotate(45deg)’**,此时就会进行数值的插值计算,从而不会丢失旋转信息,达到指望的效果。
具体的decomposing和recomposing矩阵的算法,见Graphics Gems II。
译 by Hong