W3C CSS Transforms摘译

CSS Transforms能够对一个元素进行二维平面或三维空间的变换,如translate, rotate, scale和skew等变换。css

下面是对W3C官网CSS Transforms模块的部分摘译,为了理解的连贯性,调整了W3C规范中相关章节的顺序。css3


二维子集(Two Dimensional Subset)

用户浏览器(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矩阵
二维变换的3x3矩阵

图1 二维变换3x3矩阵算法

开发者能够很简单的为不支持三维变换的浏览器提供备选变换方案。下面的例子中,transform属性有两次赋值定义。第一次定义包括了两个二维变换函数,第二次定义包括一个二维变换和一个三维变换。浏览器

div {
  transform: scale(2) rotate(45deg);
  transform: scale(2) rotate3d(0, 0, 1, 45deg);
}复制代码

当浏览器支持三维变换时,第二次属性定义将覆盖第一次的定义,当不支持三维变换时,第二次定义将会失效,浏览器会使用第一种定义。函数

常见的变换矩阵以及计算示例布局

变换渲染模型(The Transform Rendering Model)

当为元素的transform属性指定了一个非none属性值时,就会在该元素上创建了一个本地坐标系(local coordinate system)。从元素最初始的渲染(未指定transform属性值)到本地坐标系的映射由该元素的变换矩阵(transformation matrix)给定。变换是可累积的,也就是说,元素是在它们祖先的坐标系中创建本身的本地坐标系。从用户的角度来看,就是一个元素会累积应用全部它祖先‘transform’属性设置的变换,以及自身"transform"属性设置的变换,规范中将这些变换的累积称为元素当前变换矩阵(current transformation matrix, CTM)ui

坐标空间是一个有两个轴的坐标系:X轴是水平向右增长,Y轴是垂直向下增长。三维变换函数将这个坐标空间扩展到三维,增长了垂直于屏幕平面一个Z轴,而且指向观察者。spa

初始坐标空间示例
初始坐标空间示例

图2 初始坐标空间示例

变换矩阵是基于'transform''transform-origin'属性,按照如下步骤计算而来:

  1. 从单位矩阵开始
  2. 首先按照'transform-origin'属性的X,Y,Z 值进行位移
  3. 从左至右依次乘以'transform'属性中指定的各个变换函数
  4. 最后再按照'transform-origin'属性的X,Y,Z 值的负值进行位移

注意,变换只是影响了元素的显示,不影响元素自身CSS的布局。意味着变化不会影响getClientRects()getBoundingClientRect()的值。对于基于CSS盒模型布局定位的元素,若是该元素的transform属性为非none,则该元素将成为其全部fixed定位的后代元素的包含块(Containing Block)

示例1示例2示例3

变换函数的元和派生(Transform function primitives and derivatives)

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()

对于同时具备二维和三维元变换的派生变换,具体是使用二维元变换或三维的元变换,是由上下文环境来决定。

元变换函数和派生变换函数的插值(Interpolation of primitives and derived transform functions)

两个具备相同数量参数的变换函数,会直接进行数值的插值计算,而不须要转换为相同的元变换。插值计算的结果便是带有相同参数的相同变换。对于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的矩阵,而后对矩阵进行插值计算。

变换的插值(Interpolation of Transforms)

当变换函数之间发生过渡时(好比对transforms施加transition属性),就须要对变换函数进行插值计算。从一个初始的变换(from-transform)到一个结束的变换(to-transform),如何进行插值计算须要遵循下面的规则。

I. 当from-transform和to-transform的值都为none

此时没有必要进行计算,保持原值。

||. 当from-transform和to-transform中有一个的值为none

值为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-transformscale(2)to-transformnone, 则none将会被替换为scale(1),而后继续按照下面的规则进行插值。相似的,若是from-transformnoneto-transformscale(2) rotate(50deg),则from-transform会被替换为scale(1) rotate(0)

III. 若是from-transform和to-transform中都使用了相同数量的变换函数,而且各个对应的变化是相同的变换或是从相同的元变换派生的变换。

按照元变换函数和派生变换函数的插值里面的步骤,对from-transformto-transform对应的变换进行插值。计算出的结果做为最终的变换。

例如,from-transformscale(1) translate(0)to-transformtranslate(100px) scale(2),虽然都是用了scale和translate变换,可是对应的变换既不相同也不是从相同的元变换派生出来的,此时就不能按照本条规则来进行插值计算。

IV. 全部其余状况

from-transformto-transform中使用的变换都会被转化为4x4的矩阵,并进行相应的矩阵插值计算。若是from-transformto-transform对应的变换矩阵均可以表示成3x2矩阵或者matrix3d矩阵,则元素就按照相应插值计算的矩阵进行变换渲染。在某些特殊的状况下,变化矩阵多是一个奇异或不可逆矩阵,则元素将不会被渲染。

矩阵的插值(Interpolation of Matrices)

当对两个矩阵进行插值时,首先将矩阵分解为一系列变换操做的值,好比对应的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)’**,此时就会进行数值的插值计算,从而不会丢失旋转信息,达到指望的效果。

示例4

具体的decomposing和recomposing矩阵的算法,见Graphics Gems II

译 by Hong

相关文章
相关标签/搜索