Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

W3C CSS Transforms摘译 #31

Open
llwanghong opened this issue Apr 28, 2023 · 0 comments
Open

W3C CSS Transforms摘译 #31

llwanghong opened this issue Apr 28, 2023 · 0 comments

Comments

@llwanghong
Copy link
Owner

llwanghong commented Apr 28, 2023

W3C CSS Transforms

目录


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

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


5. 二维子集(Two Dimensional Subset)

用户浏览器(UAs)可能不总能渲染出三维变换,那么它们就只能支持该规范的一个二维子集。在这种情况下,三维变换和transform-style、perspective、perspective-origin以及backface-visibility属性将不被支持。三维相关的变换渲染也不会起作用。

对于二维变换情况,矩阵分解采用**Graphics Gems II(Jim Arvo著)书中unmatrix算法的二维简化版本。下面是一个二维的3x3变换矩阵,其中6个参数a~f,分别对应二维变换函数matrix(a, b, c, d, e, f)**的6个参数。

二维变换的3x3矩阵

图1 二维变换3x3矩阵

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

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

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

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

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

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

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

初始坐标空间示例

图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

18. 变换函数的元和派生(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()

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

19. 元变换函数和派生变换函数的插值(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的矩阵,然后对矩阵进行插值计算。

17. 变换的插值(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矩阵,则元素就按照相应插值计算的矩阵进行变换渲染。在某些特殊的情况下,变化矩阵可能是一个奇异或不可逆矩阵,则元素将不会被渲染。

20. 矩阵的插值(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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant