在一票教你如何制做立方体的教程以后,在张鑫旭生动的类比讲解12以后,我还想重提一次前端中的变换矩阵——它被挖掘得远远不够。
本文里,让咱们从W3C标准和浏览器等新角度来从新理解变换矩阵。javascript
在开始前,咱们不妨看动画库bouncejs来热身一下。css
一个动画若是要给人带来愉悦、动人甚至是惊艳的感受,它首先要足够贴近咱们的经验,不然咱们理解不了动画过程;其次,它还要有充沛的细节,不然会显得单调乏味。
bouncejs这个库就同时作到了这两点。html
咱们知道CSS3中的时间函数实际上是很是残缺的,它最复杂也不过是生成一个有四个参数的三次贝塞尔曲线,还远远不够咱们对于动画细腻程度的追求。若是细窥bouncejs的实现,咱们会发现它用到了线性的时间函数,而在keyframes中表达动画细节,用到了一大堆matrix3d:前端
@keyframes animation{ /* ... */ 21.32% { transform: matrix3d(2.196, 0, 0, 0, 0, 2.069, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1); } 24.32% { transform: matrix3d(2.151, 0, 0, 0, 0, 1.96, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1); } /* ... */ }
如何实现:咱们想要的动画中的回弹、硬直等效果如何拆解为这些matrix3d的呢?
背后机制:这些matrix3d是如何组合成咱们想要的动画效果的呢?java
先来看看变换矩阵在各处的表现形式吧。css3
也许是咱们第一次接触变换矩阵的地方。git
.selector { transform: matrix(a, b, c, d, e, f); transform: matrix3d(m11, m12, m13, m14, m21, m22, m23, m24, m31, m32, m33, m34, m41, m42, m43, m44); }
SVG做为可缩放矢量图形,它局限于2D坐标中,所以它只有二维变换:github
<g transform="matrix(a, b, c, d, e, f)"></g>
注意,因为SVG没有transform-origin属性,所以须要本身用translate来模拟。
可参考:web
DEMO #1
SVG相对中心翻转和相对中心旋转的实现
实现方式:三个transform的嵌套。canvas
首先是2d Context中的变换矩阵。
var context = canvas.getContext("2d"); context.transform() /*与以前的矩阵值累乘*/ context.setTransform() /*不与以前的矩阵值累乘*/
它跟SVGMatrix接口同样,所以也不支持transform-origin,须要用translate模拟。
可参考:
DEMO #2
Canvas相对中心翻转和相对中心旋转的实现
实现方式:三个transform的嵌套。
一个遗留的接口,不作多介绍。
可见:张鑫旭:IE矩阵滤镜Matrix旋转与缩放及结合transform的拓展。
咱们能够用getComputedStyle
来获取到相应的transform参数,得到的值是一个字符串。
window.getComputedStyle(dom).transform window.getComputedStyle(dom).webkitTransform
webkit浏览器和IE曾经支持私有的包装器(WebkitCSSMatrix
和MSCSSMatrix
),能够在获得字符串之后协助咱们作一些矩阵运算,但目前已被浏览器废弃。
咱们能够本身实现一个相似的包装器,可参考github: CSSMatrix。
此外,在THREE.JS中也存在包装器THREE.Matrix4
。
变换矩阵的实质为一组线性变换的系数矩阵。变换的目标为坐标。
系数矩阵的应用方式,在于其用于左乘点向量的齐次坐标。
当算出的齐次坐标值不为1的时候,须要完成齐次除法 homogeneous divide,使得第四个值为1,以算出最终的坐标值。
计算过程以下图:
最后,一个变换矩阵不只仅是描述坐标点变换,其实也描述了坐标系变换(基变换)。
须要注意的是,变换矩阵的乘法是不符合乘法交换律的。可参考:
DEMO #3
从新排序变换矩阵/函数。
测试方法:改变上方或下方的transform中的rotateY和rotateX的顺序,比较他们的矩阵和显示效果
可见,交换律是不符合的。
一些特殊状况下,矩阵A乘以矩阵B正好等于矩阵B乘以矩阵A。
常规的例子是单位矩阵E左乘或右乘A,都将获得A。
一个很简单的理解:单位矩阵其实表明了x'=x
、y'=y
...的方程组的系数矩阵。
再怎么变换,变换矩阵都是一个线性变换,没法将直线变成曲线。因此鱼眼之类的效果不能简单的用变换矩阵来完成。
transform-origin
上文已有DEMO实例。变换中心点功能是由变换矩阵左乘坐标位移矩阵P,和右乘坐标位移矩阵的逆矩阵P-1,来影响结果的:
perspective
、perspective-origin
从CSSTricks里面借一张图:
上图中眼睛位置相关的三个坐标能够借由这些属性调整:
p:perspective
眼睛的x和y:perspective-origin
transform-style
transform
translate
scale
rotate
skew
matrix
translate3d
scale3d
rotate3d
perspective
matrix3d
计算变换矩阵
从单位矩阵开始
乘以transform-origin的坐标系变换矩阵
按照声明顺序,乘以每个transform function其对应矩阵
乘以transform-origin的逆坐标系变换矩阵
计算累计变换矩阵
对于该元素和3D渲染上下文根元素的每个包含块,从单位矩阵开始
乘以其包含块上的perspective矩阵
乘以当前块相对其包含块的水平、垂直位移矩阵
将累积变换矩阵乘以变换矩阵,获得最终变换矩阵
日常动画过程当中,有两个概念:
Timing function 时间函数:由时间函数f获得插值比例。t=f(tNow, tTotal)
tNow:通过时间
tTotal:总时间
Interpolation:插值函数:由插值函数g获得最终的值。(多半是线性插值)s=g(t, xStart, xEnd)
t:插值比例
xStart:开始值
xEnd:结束值
对于变换矩阵,会采用线性的插值方式吗?请参考:
DEMO #4
矩阵的插值实验
实验方式:左边为用js实现的线性插值,右边为用animation实现的方法,起止皆为matrix。
测试方法:点击“combine”,而后再点击“play”。
能够由轮廓的不重合之处发现,线性的插值是不许确的插值方式。
根据标准,矩阵插值的方式是这样的:
Decomposing:将矩阵分解为多个子变换,并求得对应的向量。
perspective
translate
quaternion(四元数)
skew
scale
分别对各个子变换进行插值计算
四元数向量的插值方法为球面线性插值spherical linear interpolation
其它的子变换都经过简单的线性插值计算
Recomposing:插值结束之后,将各个子矩阵按顺序相乘,获得最终的矩阵。
单位矩阵
乘以perspective matrix
乘以translation matrix
乘以rotation matrix
乘以skew matrix
乘以scale matrix
to be continued
了解了数学中的相关概念,咱们便可参考源码中变换矩阵的实现方式了。
包含了对矩阵自己的定义、矩阵相关的数据类型和矩阵的基础计算。
这个类是矩阵相关的最基础的4*4的矩阵数据结构。
源码:
包含:
一个SkMScalar
类,根据编译选项可用于表示float或double;
set*()
为设置当前矩阵为某变换对应的矩阵,而pre*()
和post*()
则为在某项变换以后或以前的变换操做,也能够理解为某个变换矩阵的左乘或右乘;
determinant()
为求矩阵的行列式;
invert()
求逆矩阵;
transpose()
转置矩阵;
computeTypeMask()
方法展现了如何经过计算获得一个变换矩阵是否包含以下变换:
位移:translate
伸缩:scale
仿射:affine,包含旋转 rotate 和斜切 skew
透视:perspective
这个类映射到CSS中的transform声明。
源码:
包含:
2d、3d的构造函数、拷贝构造函数、一些运算符重载
针对CSS中的各种声明方式,获得对应的变换。包括但不限于:Translate();
Translate3d();
Scale();
Scale3d();
RotateAboutXAxis();
RotateAbout();
应用透视 perspectiveApplyPerspectiveDepth()
左乘和右乘PreconcatTransform();
ConcatTransform();
对矩阵性质的判断IsScale2d()
IsApproximatelyIdentityOrTranslation()
其余矩阵操做,如转置和获得逆变换GetInverse()
Transpose();
矩阵插值操做Blend()
包含变换相关的一些数学计算功能、除了矩阵之外的数据类型定义。
源码:
包含:
点类Point
和矩形类Rect
DecomposedTransform
矩阵插值过程当中的类,包含各个变换相关的特征值向量:
位移向量:translate[3]
缩放向量:scale[3]
斜切向量:skew[3]
透视向量:perspective[4]
四元数向量:quaternion[4]
,用于旋转。不用常规的三变量表示方法是为了不欧拉锁问题。
一些辅助方法
由矩阵拆解为DecomposedTransform
的方法DecomposeTransform()
方法BlendDecomposedTransforms()
,由DecomposeTransform
参与的插值方法
方法Slerp()
,用于四元数的球面插值方法
获得三元向量的长度Length3()
向量点乘Dot()
将矩阵归一化Normalize()
应用变换中心的方法TransformAboutPivot