本篇涵盖第 24-26 章,从 CSS transform 到 perspective 再到数学库集成,完整讲解 CSS 3D 动画技术。
前面介绍了 3D 变换的原理和算法实现,并通过一些简单的 demo 演示了变换效果,但这些 demo 都是使用 WebGL 技术渲染。本节暂时不使用 WebGL,而是改用前端同学最熟悉的 CSS 技术来实现 3D 效果,并进一步了解 CSS 中的 3D 属性和 WebGL 中 3D 概念的异同之处。
下面是 CSS3 中的几个很重要的 3D 属性:
本节主要讲述 CSS 中的变换属性transform,变换分为基本变换和矩阵变换,基本变换大家都比较熟悉了,本节不做过多介绍,我们主要介绍矩阵变换和组合变换。
transform 是大家最常用的一个属性,我们经常会使用它实现一像素的边框和以及容器或者内容的水平、垂直居中,又或者利用它实现强制 GPU 渲染,提升动画性能。
transform 分为 2D 和 3D 变换,3D 变换只是在 2D 的基础上增加了 Z 轴方向的变换。
一般情况下,如果对一个 DOM 施加变换,那么变换的中心往往是 DOM 的中心位置,类比到 WebGL 中,也就是模型的中心,我们可以把 CSS 中的 DOM 看做 WebGL 中的模型。
transform 包含四个基本变换属性值:translate、rotate、skew、scale,对应的 3D 变换属性值为 translate3d、rotate3d、scale3d。
注意,skew 没有对应的 3D 变换设置。
基本变换大家应该都很熟悉了,后面重点要讲解的是 matrix 、matrix3d的计算与使用,以及组合变换的使用技巧。
当然,下面我们还是先回顾一下 transform 的基本用法。
平移的使用方法:
将 dom 元素分别沿着 X、Y、Z 轴向平移 30 px。
/* 分别沿 X 轴和 Y 轴平移 30 px。*/
transform: translate(30px, 30px);
/* 分别沿 X 轴、 Y 轴、Z 轴平移 30 px。*/
transform: translate3d(30px, 30px, 30px);
/* 沿 X 轴平移 30 px。*/
transform: translateX(30px);
/* 沿 Y 轴平移 30 px。*/
transform: translateY(30px);
/* 沿 Z 轴平移 30 px。*/
transform: translateZ(30px);旋转用法也比较简单,在此不做过多描述。
axis = {x:x, y:y, z:z}旋转指定角度angle。/*绕 Z 轴旋转 45 deg。*/
transform: rotate(45deg);
/* 将第一个参数设置为 1, 代表绕 X 轴旋转 45 deg。*/
transform: rotate3d(1, 0, 0, 45deg);
/* 将第二个参数设置为 1, 代表绕 Y 轴旋转 45 deg。*/
transform: rotate3d(0, 1, 0, 45deg);
/* 将第三个参数设置为 1, 代表绕 Z 轴旋转 45 deg。*/
transform: rotate3d(0, 0, 1, 45deg);
/* 使用 rotateZ 绕 Z 轴旋转 45 deg。*/
transform: rotateZ(45deg);关于旋转,我想说明一下rotate3d 的使用方式,它接收一个轴向量和一个角度,代表绕轴向量旋转某个角度。
举个例子来说,我们想让模型绕轴 axis= {x: 1,y: 1,z: 1}进行旋转,那么用rotate3d表示如下:
.box{
animation: rotate 3s infinite linear;
}
@keyframes rotate{
0% {
transform:rotate3d(1, 1, 1, 0deg);
}
100% {
transform:rotate3d(1, 1, 1, 360deg);
}
}根据上图的例子,你会发现,默认的旋转是绕着模型的中心位置进行的,这个位置是浏览器默认的。但事实上,CSS 仍然提供了对变换中心的设置功能,通过设置 transform-origin 来实现。
比如,我们让一个 DOM 元素沿着上边沿进行旋转,只需要将 transform-origin 的 Y 轴分量设置为 0% 或者 0 即可。
transform-origin: 50% 0%;
transform: rotateX(90deg);这个概念比较简单,但是很灵活。利用它我们能实现很多有意思的 3D 效果,比如 CSS 版的魔方。
缩放的使用方法也很简单。
代码示例:
/* 沿 X 轴和 Y 轴 放大两倍。*/
transform: scale(2);
/* 沿 X 轴方向放大 3 倍,沿 Y 轴方向放大 2 倍。*/
transform: scale(3, 2);
/* 分别在 X 、Y、 Z 轴方向放大 2倍、3倍、4倍。*/
transform: scale3d(2, 3, 4);
/* 在 X 轴方向放大两倍。*/
transform: scaleX(2);
/* 在 Y 轴方向放大两倍。*/
transform: scaleY(2);斜切的使用方法:
斜切可以理解为将 DOM 元素沿 X 轴或者 Y 轴拉伸,切变会改变物体的形状。
代码示例:
/* 沿 X 轴切变 30 度。*/
transform: skew(30deg);
/* 沿 X 轴切变 30 度,沿 Y 轴切变 40 度。*/
transform: skew(30deg, 40deg);
/* 沿 X 轴方向切变 30 度。*/
transform: skewX(30deg);
/* 沿 Y 轴方向切变 40 度。*/
transform: skewY(40deg);以上就是 transform 的常见用法,接下来我们开始讲重点了:组合变换和 matrix
组合变换就是在 transform 的属性值中附加多个变换效果,而不只是单一的变换。
比如下面这个变换样式:
transform: rotateX(60deg) rotateY(60deg);这个样式的作用是先让 DOM 元素绕着 X 轴旋转 60 度,注意此时 DOM 元素的坐标系改变了,再绕变换后的坐标系的 Y 轴旋转 60 度。
需要谨记的是:
动态坐标系变换。静态坐标系变换。至于多个变换是基于动态坐标系进行构思,还是基于静态的世界坐标系进行构思,取决于每个人的理解习惯,但最终的变换效果都是一样的。
在欧拉角章节我们也讲过了多个矩阵相乘时,从前往后和从后往前理解变换所基于的坐标系是不同的。transform 多个变换理解顺序和前面所讲的保持一致。
再举个比较明显的例子,我们先让 DOM 旋转 60 度,然后将其沿 X 轴平移 200 像素,大家觉得 DOM 会按照怎样的轨迹变换?
我们看一下:
transform: rotateX(60deg) translateX(200px);为了更方便观察 3D 组合变换的效果,我将图片外层容器的视点设置在了右上方:
.imgWrapper{
perspective: 300px;
margin-top:300px;
position: relative;
perspective-origin: 100% -100px;
}关于视点 perspective-origin 和 视距 perspective 我们放在下一节讲述。
模型坐标系理解:下图,白色坐标轴是图片默认的坐标系,当图片绕 X 轴旋转 60 度后,坐标系变成红色坐标轴的指向。
接着沿当前图片坐标系的红色 X' 轴平移 200 像素,此时,应该朝向屏幕里侧和右侧移动。
请注意,CSS 中的世界坐标系就是 施加变换的 DOM 节点(本例为图片)最开始的坐标系,即图中的白色坐标轴。
可以看出,无论我们按照哪种坐标系理解,最终变换效果都是一样的,看下图:
这就是组合变换要注意的地方,了解了组合变换的规律,我们才能做出很有意思的特效,比如照片墙:
照片墙的核心原理就是先将图片沿着 Z 轴方向移动一定距离,之后绕世界坐标系的Y 轴旋转指定角度。
transform: rotateY(30deg) translateZ(200px);当然,这只是核心原理,事实上我们还需要做如下几步:
transform-style 属性设置为 preserve-3d。perspective,设置为透视投影,并调整合适的视距 ,这样才能实现近大远小的效果。这几个属性我们下节细讲,先贴下照片墙的效果:
具体的实现我们在讲视点和视距属性时再分析。
又比如 3D 盒效果:
这是一个立方体盒子,每个面上都对应一张图片,当然,你也可以根据自己的需要往各个面上放置自己的内容。
立方体盒子的原理也是利用组合变换实现的:
当然,为了实现 3D 效果,也要在图片的父容器上设置 transform-style 为 preserve-3d 才可以。
这里主要展示组合变换顺序的理解与强大,不对实现做分析,实现过程留在下一节和视点以及视距一起介绍。
我们还是先把变换讲完,接下来介绍 transform 的另一个重要用法,matrix 和 matrix3d。
transform 除了提供一些基本变换,还提供了 matrix 和 matrix3d 属性值,这两个属性值是做什么的呢?
matrix 是矩阵的意思,transform 是变换属性,所以 matrix 就是对 DOM 执行变换的矩阵,这和我们前面讲的 WebGL 的变换矩阵概念相同。
根据前面的学习,我们知道 2 维平面的变换矩阵,是一个 3 阶矩阵,包含 9 个数字:
按照我们之前章节坐标系变换的原理分析:
如果没有看之前章节的话,可能不太理解基向量的含义以及坐标系的概念,大家可以去看一下,理解一下坐标系变换的原理。
你会看到第三行的数值是固定的0 0 1,所以,浏览器为了简化赋值,规定 transform 中的 matrix 只接受 3 阶矩阵的前两行参数,共 6 个数字。
请记住,matrix 的参数顺序对应上面的矩阵元素如下:
transform: matrix(x0, x1, y0, y1, tx, ty);前面章节我们推导过基本变换的矩阵表示:
沿 X 轴平移 tx 像素,沿 Y 轴平移 ty 像素:
沿 X 轴方向缩放 sx 倍, 沿 Y 轴缩放 sy 倍:
也就是说基本变换我们都可以用 matrix 来表示。
绕 Z 轴旋转 θ 角度:
用 rotateZ 表示上面的旋转很简单,我们看下用 matrix 如何表示:
将这两个数字代入 matrix 公式,得出变换样式为:
transform: matrix(0.5, 0.866, -0.866, 0.5, 0, 0);你会发现无论我们是用 rotateZ 表示,还是用 matrix 表示,变换效果都是一样的。
matrix3d,顾名思义,代表 3 维变换,它是一个 4 阶矩阵,需要 16 个数字来表示。
可以看到,每一个基向量都增加了一个 Z 轴方向分量 x2、y2、z2、tz。
事实上,3D 的基本变换都可以用 matrix 或者 matrix3d 来表示,但是一些复杂变换只能使用 matrix 或者 matrix3d 来实现。
这是一个很关键的问题,大家会觉得,基本变换的用法更容易理解,更方便书写,matrix 需要的参数太多,而且参数值需要计算,更糟糕的是,我们往往不知道怎么计算,那 matrix 有什么用呢?
这是个好问题,但我想说的是 matrix 能完成基本变换不能完成的变换,能做出基本变换完成不了的效果。
这时你就该考虑使用 matrix 了。
紧跟而来的问题是,matrix 如何求得呢?
我们前面章节讲述了 matrix 矩阵的求法,一旦你确定了需要的变换,你就可以计算变换后的基向量,然后将基向量的各个分量代入矩阵的各个位置即可求出变换矩阵,有了变换矩阵,也就有了 matrix 所需要的各个元素,将矩阵转化成 matrix 或者 matrix3d 所需要的字符串就轻而易举了。
镜像效果,采用基本变换是实现不了的,只能借助于矩阵。 比如左右镜像,左右镜像无非就是 Y 轴基向量不变,X 轴坐标对调,原坐标与新坐标关系如下:
所以有:
将这些值,代入 matrix公式中,可以得出变换:
transform: matrix(-1,0,0,1,0,0);我们看下效果:
当然大家也可以举一反三,比如上下镜像:
transform: matrix(1,0,0,-1,0,0);绕任意轴的旋转,除了可以使用 rotate3d 来实现,还可以使用 matrix3d。这个旋转矩阵该如何计算呢?
还记得基本变换章节我们推导出的绕固定轴旋转的矩阵方法axisRotation吗?
axisRotation(axis, angle),其中 axis 是一个三维向量,angle 是一个弧度值。
这里我不准备举绕基本轴旋转的例子,因为它们不需要matrix3d 出手,我们想绕{x:1, y: 1, z: 1}的倾斜轴旋转,上面的 axisRotation 方法该出场了,它会为我们提供变换矩阵,但是我们还需要将变换矩阵转化为 css 样式,我们先封装一个矩阵转 css 的方法:
function matrix2css(mt){
var transformStyle = 'matrix(';
if(mt.length == 16){
transformStyle = 'matrix3d(';
}
for(let i =0; i< mt.length; i++){
transformStyle += mt[i];
if(i !== mt.length - 1){
transformStyle += ',';
}else{
transformStyle += ')';
}
}
return transformStyle;
}接着可以通过 axisRotation 方法计算出变换矩阵了,比如旋转 90 度。
let mt = matrix.axisRotation({x:1, y:1, z:1}, Math.PI / 180 * 90)在这里我用绕轴向量 axis = {x:1, y:1, z:1} 不停旋转的动画演示:
function render(){
if(!playing){
return;
}
angle ++;
mt = matrix.axisRotation({x: 1,y: 1,z: 1}, Math.PI / 180 * angle);
let css = matrix2css(mt);
$('.box')[0].style.transform = css;
requestAnimationFrame(render);
}
document.body.addEventListener('click',function(){
playing = !playing;
render();
})是不是很简单呢?
transform 中的基本变换都可以使用 matrix 和 matrix3d 来表示,只有当基本变换表示不了我们的变换时,我们才考虑使用 matrix 或者 matrix3d 。
可见,即使不做 WebGL 开发,我们之前学到的内容也会对普通开发者大有帮助,掌握变换原理,配合 CSS3 中的 3D 属性,照样可以做出很酷炫的 3D 动画效果。
本节讲述了 CSS 中的 3D 变换,以及它们与 WebGL 变换的相同之处。总的来说,基本原理都是一样的,之前封装的数学矩阵库,不仅可以用在 WebGL 领域,也可以用在 css 领域。
下一节,我们讲述 CSS 中 3D 变换 的其它几个重要属性,变换类型(transform-style)、视点(perspective-origin)、视距(perspective)。
上节我们讲述了 CSS 中的变换方式以及对它们原理的深入理解。本节介绍一下 3D 变换的投影方式perspective相关属性,并通过照片墙和图片盒的实现学习它们的深入使用方法。
本节要介绍的主要属性有如下几个:
本节教大家掌握这几个属性,之后用 CSS 就能够很轻松地做出有创意的 3D 效果了。
该属性是用来设置子元素变换的展示形式,默认是 2D 平面展示,当我们需要让子元素的渲染有 3D 效果时,那么我们要将当前元素的 transform-style 属性设置为 preserve-3d。
我们用一张图片做示例:
<div class="img-wrapper">
<img src="xxx" />
</div>.img-wrapper{
text-align: center;
font-size: 0;
margin-top: 200px;
}
.img-wrapper img{
width: 150px;
height: 110px;
}我们不对图片施加变换,图片是正常状态:
记住图片默认时的样子。
此时,我们对图片施加变换,让图片绕 X 轴旋转 60 度。
.img-wrapper img{
transform: rotateX(60deg);
}可以看出,图片因为旋转,展示到屏幕上已经变形了,正常情况下,绕 X 轴旋转,图片的上半部分应该转到屏幕内侧,下半部分转到屏幕外侧。
我们看一下,是不是这样子的。
我们让图片的父容器也绕 X 轴旋转,这样就能看出图片到底是不是真的有了 3D 效果。
.img-wrapper{
transform: rotateX(0deg);
animation: rotate 3s linear;
}
@keyframes rotate{
0%{
transform: rotateX(0deg);
}
50%{
transform: rotateX(120deg);
}
100%{
transform: rotateX(0deg);
}
}可以看到,图片并没有呈现出立体效果。
那如何让图片呈现立体效果呢?
transform-style 要闪亮登场了。
我们将 transform-style 设置为 preserve-3d 即可。
另一个问题是,为哪个元素设置 transform-style 属性呢?
WebGL 中有舞台和场景的概念, CSS 中也有类似概念。
我们需要一个舞台,然后为舞台设置展示方式。
上面的示例,大家觉得哪个元素是舞台呢?
很简单,图片的祖先容器都可以当做图片的舞台,我们将一个祖先容器设定为舞台,祖先容器的子元素就是舞台上的元素。
因此,既然我们想让元素呈现立体效果,我们就要在元素所在的舞台上设置 transform-style 属性,对于图片来说,图片的任何一个祖先元素都可以作为舞台,我们将图片的父容器作为舞台,为它设置舞台元素的呈现效果。
.img-wrapper{
transform-style: preserve-3d;
}很明显,图片呈现出了 3D效果。
通过这两幅动图,我相信大家已经明白 transform-style 的作用了。
我们总结一下:
transform-style用来设置容器中子元素的呈现方式。
flat 时,子元素不管采用 2D 变换还是 3D 变换,总是呈现出平面效果。如何改变观察点位置呢?
这就涉及到两个新属性 perspective和perspective-origin,这两个属性用来设置观察点的坐标,并让投影产生透视效果,透视效果最明显的现象就是近大远小。
perspective 用来设置观察点在 Z 轴方向的位置,默认时观察点在元素中心,即 z = 0 位置,我们可以调整它到元素中心的距离。
设置这个属性会产生两个效果:
我们想换个角度看图片,那观察点需要设置在哪个元素上呢?
perspective 这个属性也是在舞台上设置的。
我们为舞台设置 perspective属性。
请注意,perspective 默认值是 0,此时不产生透视效果。只有不为 0 时才会形成透视效果。
那么,观察点距离元素中心的远近对视觉呈现有什么影响呢?
千言万语抵不过一幅图。
我们通过一张动图感受一下,在这个动画里,我将观察点的 Z 轴坐标从 1 像素匀速改变到 100 像素,再从 100 像素移近到 1 像素,另外为了区别舞台和舞台元素在 Z 轴上的不同,我将舞台的背景颜色改为透明度为 70% 的红色。
.img-wrapper{
perspective: 300px;
animation: camera 3s linear;
background-color: rgba(255, 0, 0, .7);
}
@keyframes camera {
0% {
perspective: 1px;
}
50% {
perspective: 100px;
}
100% {
perspective: 1px;
}
}因为图片宽度是 150px,高度是 110px,当绕 X 轴旋转 60 度后,图片底边在 Z 轴的坐标为:
所以,我们的观察点的 Z 轴坐标至少要大于这个值,才能看到图片的全貌。
距离越近,就越看不到图片在观察点后面的部分。
我们将 perspective (观察点的 Z 轴坐标)移动到 100px 处。
此时,图片有了近大远小的透视效果,这就是 perspective 的作用。
从感官上我们是能感受到它好像有了 3D 的感觉,但是还是看不出它在 Z 轴上的状态。
改变 perspective 只是改变了在 Z 轴的位置,也就是拉远或者拉近镜头,但是并没有将镜头移到图片上方去观察。
让观察点沿着左、右、上、下方向进行移动的话,该如何做呢?
这就是 perspective-origin 所扮演的重要角色。
它负责设置观察点在 X、Y 轴的偏移,即屏幕的左右、上下方向。
我们看下它的用法:
perspective-origin,接收两个参数,一个代表 X 轴偏移,一个代表 Y 轴偏移。
请谨记:该属性的默认值是 (50%,50%),在舞台 DOM 的中心位置。
回到上面的话题,移动镜头到图片上方,也就是设置观察点在舞台上的 Y 轴坐标,我们将镜头往上移动一定高度,从舞台上方观察图片。
还是用动图来演示这个过程。
.img-wrapper{
perspective: 200px;
animation: camera 3s linear infinite;
}
@keyframes camera{
0%{
perspective-origin: 50% 50%;
}
50%{
perspective-origin: 50% -200%;
}
100%{
perspective-origin: 50% 50%;
}
}上面就是我们将镜头慢慢移动到上方时,对舞台元素的观察过程。
注意,图片本身并没有动,我们移动的只是镜头,虽然你会有一种镜头没动,图片在动的感觉。
通过这几幅动图,我相信大家对这几个属性有了更深刻的认识,接下来,我们介绍如何利用这几个属性实现照片墙和图片盒。
在上一节,我介绍了照片墙的原理,简单来说,就是图片先旋转一定度数,之后沿着 Z 轴方向移动一定距离。
在动手写代码之前,我们要想清楚两个问题:
我想,只要你想清楚了这两个问题,代码就信手拈来了。
我们准备 12 张图片,并将每张图片设置成宽 150px,高 110px 。
img{
width: 150px;
height: 110px;
}那么,每张图片旋转的角度是 360 / 12 = 30度。
为了让图片能够不交叉,我们需要沿图片 Z 轴平移一定距离,那么,这个距离是多少呢?
当然我们可以一点点地区尝试,但是我想我还是教给大家一个严谨的思路比较好。
大家看下图:
根据下图,我们能够知道,图片在 Z 轴平移的最小距离是 ,那么如何求得
的长度呢?
由最简单的三角公式可以得出:
仍然根据三角公式可以知道:
所以:
也就是说,我们至少要让图片沿着 Z 轴移动 280 像素,才能让各个图片正好衔接。
口说无凭,看看效果吧。
Array.prototype.forEach.call($('img'), (item, i) => {
item.style.transform = 'rotateY(' + i * 30 + 'deg) translateZ(280px)';
});仍然用一幅动图来演示,我们将观察点移动到舞台上方:
可以看到,当我们让图片沿Z 轴移动 280 像素之后,各个图片的边缘正好能够对齐,和我们推导的结果一致。
将舞台绕 X 轴旋转 90 度,以自下而上的角度观察照片墙是如何从默认位置沿着 Z 轴移动到指定位置:
.img-wrapper{
transform: rotateX(90deg);
}从不同角度观看我们的舞台,展现效果也会不同。
照片墙这个效果,大家应该能看到处于背面的元素还是能够可见的,这与实际有所差异,如果大家不想背面元素可见,我们可以设置 backface-visibility 属性来实现:
backface-visibility: hidden;大家可以看到,设置完背面不可见属性之后,位于后面的图片此时已经看不到了。
以上就是图片墙的主要原理,可见,了解一些图形学知识对于我们做 3D 动效有莫大的帮助。
接下来,我们看一下另一个效果图片盒子的分析实现过程。
图片盒是一个包含 6 个面的立方体,每个面其实就是一个 DOM 元素。
上图就是一个图片盒子的例子,我们接下来的目标就是利用 CSS 的 3D 属性实现这样一个效果。
写代码之前还是先思考一下,不考虑动效,我们如何实现这样一个立方体。
其实很简单,主要思考清楚六个面的状态就可以了。
想清楚这几个步骤,代码就很简单了。
为了让图片展示 3D 效果,我们需要为图片父容器设置 transform-style 属性:
.img-wrapper{
transform-style: preserve-3d;
position: relative;
}接下来,我们为图片元素设置宽高,并对六张图片采用绝对定位,使他们重合。
img{
width: 150px;
height: 110px;
position: absolute;
}然后,开始处理各个面的状态,对于图片盒子的前后两个面,我们只需要改变它们的 Z 轴坐标就可以了,那么 Z 轴坐标设置多少呢?
聪明的同学已经想到了, Z 轴坐标是图片高度的一半。
.front{
transform: translateZ(55px);
}
.back{
transform: translateZ(-55px);
}前后平面处理完了,接下来处理上下两个面,上下两个面要绕 X 轴翻转 90 度,然后沿着 Y 轴分别向上下两个方向移动图片高度的一半距离。
.top{
transform: translateY(-55px) rotateX(90deg);
}
.bottom{
transform: translateY(55px) rotateX(90deg);
}最后,处理左右两个平面,左右两个平面需要绕 Y 轴旋转 90 度,然后沿着 X 轴进行平移,分别向左、向右平移图片宽度的一半。
.left{
transform: translateX(-75px) rotateY(90deg);
}
.right {
transform: translateX(75px) rotateY(-90deg);
}这样,就完成了一个图片盒子,很简单吧?
本节主要讲述了如何设置观察点在三维层面的坐标,并结合上节的变换属性实现照片墙和图片盒子的特效,主要目的不是教会大家做这些效果,而是教大家思考 3D 特效的分析过程。通过这两个例子,我想大家认识到了 css 的强大。即使不使用 WebGL ,我们依然可以用 DOM 元素结合 CSS 实现 3D 效果。
下一节,我们详细阐述一下数学库在 CSS 3D 属性中的高级应用。
前面两节,我们详细讲述了CSS 3D 属性的相关概念与使用方法,本节看一下 3D 数学库如何与 CSS 中的 transform 属性实现复杂的 3D 效果。
我们依然从最简单的平移、旋转、缩放开始演示,但是不再使用 translate、scale、rotate 等属性来实现,而是采用 matrix 和 matrix3d。
作为 transform 属性最冷门的两个属性值,我想大家很少有人会用到它们,或者不知道它们是做什么的,以及不知道该如何使用它们。
上一节我简单介绍了 matrix 和 matrix3d 的使用方法,本节我会详细介绍一下它们。
matrix 是 transform 的 2 维变换矩阵,由六个数字组成。
transform: matrix(a, b, c, d, e, f);
事实上,二维变换矩阵是一个 3 阶矩阵,包含 3 x 3 = 9 个数字,但是你会发现 matrix 是由 6 个数字组成,它们对应一个 3 阶矩阵的如下部分:
很容易理解,由于第三行的数字始终是固定的,所以 css 规范中把第三行给省略了,matrix 只需要接收前两行数字即可。
对于基本变换,matrix 各个元素表示如下:
如果 matrix 只表示平移时,只需要改变 tx 和 ty 即可,其中 tx 代表沿着 X 轴平移的像素值,ty 代表沿着 Y 轴平移的像素值。其余元素都是固定值,仅仅改变 tx 和 ty即可。
比如,让一个 dom 元素沿着 X 轴平移 30 像素,沿着 Y 轴平移 40 像素,那么,用 matrix 表示如下:
transform: matrix(1, 0, 0, 1, 30, 40);注意,
tx和ty只能用数字表示,后面不可接px。
单一矩阵表示缩放时,各个位置的元素如下:
其中 sx 代表 X 轴方向的缩放比例,sy 代表 Y 轴方向的缩放比例,其余位置的元素都是固定的。 比如,沿着 X 轴放大两倍,沿着 Y 轴放大两倍时,css 表示如下:
transform: matrix(2, 0, 0, 2, 0, 0);等价于
transform: scale(2);或
transform: scale(2, 2);当我们想通过 matrix 设置缩放比例时,只需要改变 sx、sy 即可。
单一矩阵表示旋转时,各个位置元素表示如下:
由于 2D 旋转是在 XY 平面的旋转,也就是绕 Z 轴的旋转,所以 θ 角度是绕 Z 轴旋转的角度。 比如旋转 45 度时:
将各个数字代入公式后,css 表示如下:
transform: matrix(0.7071, 0.7071, -0.7071, 0.7071, 0, 0);等价于
transform: rotate(45deg);
transform: rotateZ(45deg);以上就是通过单一矩阵介绍了 matrix 的用法。大家可能会有如下疑惑:transform 已经内置了基本变换属性translate、rotate、scale,所以,我们为什么还要用 matrix?况且 matrix 更复杂,更不易于理解。回答这个问题之前,我们趁热打铁,先了解下 3 维变换 matrix3d 的用法。
顾名思义,matrix3d 是transform 的 3 维变换矩阵,由 16 个数字组成。
transform: matrix3d(a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p);
CSS 中 matrix3d 的参数顺序对应变换矩阵的元素位置如下图所示。
16 个数字乍看之下很多,初学者容易犯晕,不过大家可以根据上图对这 16 个数字进行分类,分为四组,每组 4 个数字,每组数字代表变换矩阵的每一列,每一列数字代表的含义如下:
如果你看过在之前章节坐标系变换原理的话,相信你会很容易理解上面的解释,如果你没看过,那也没关系,只需要掌握数学矩阵库的使用即可生成这么一个变换矩阵,之后将矩阵的各个元素填入 matrix3d 的 对应位置即可。
接下来看一下 matrix3d 是如何达到 transform 基本变换效果的。
3D 平移无非就是增加了一个 Z 轴的平移效果。
平移时,matrix3d 对应变换矩阵的各个元素位置如下:
其中:
如果,我们想通过 matrix3d 表示平移的话,只需要改变 tx、ty、tz 三个元素即可,其余元素如上图,无需变化。
假设我们要实现沿 X 轴平移 30 像素,沿 Y 轴平移 40 像素,沿 Z 轴平移 50 像素,那么 CSS 可以像下面这样设置:
transform: matrix3d(
1, 0, 0, 0,
0, 1, 0, 0,
0, 0, 1, 0,
30, 40, 50, 1
);等价于
transform: translate3d(30px, 40px, 50px);注意哦,当使用 translate 时需要带上单位 px。
大家仍然不难发现,使用 matrix3d 比 translate 复杂,要书写的属性内容也多,并且不易于理解。
3D 缩放仍然增加了 Z 轴方向的缩放效果,当 transform-style 设置为 preserve-3d 时,能够看到缩放 Z 轴所带来的视觉效果。
缩放对应的变换矩阵各个元素的位置如下图:
其中:
除了 sx、sy、sz 这三个元素需要我们自己设置,其余位置的元素都是固定值,如上图。
假设我们要将一个 dom 元素沿 X 轴、Y 轴、Z 轴各放大两倍,那么用 matrix3d 表示如下:
transform: matrix3d(
2, 0, 0, 0,
0, 2, 0, 0,
0, 0, 2, 0,
0, 0, 0, 1
);等价于:
transform: scale3d(2, 2, 2);我们通过一个小例子,看下效果。
<div class="parent">
<div class="son"></div>
</div>.parent{
transform-style: preserve-3d;
background: bisque;
transition: 2s;
}
.parent:hover{
transform: rotateY(80deg);
}
.son{
width: 100px;
height: 100px;
background-color: blueviolet;
transform: matrix3d(1 , 0, 0, 0,
0, 1, 0, 0,
0, 0, 4, 0,
0, 0, 0, 1)
rotateX(60deg);
}为了便于观察 Z 轴放大效果,此处我对子元素做了一个多重变换,首先将子元素沿着 X 轴旋转 60 度,之后再执行缩放变换。多重变换的细节大家可以参见本章《CSS 与 3D 之 transform》中组合变换的部分。
我们将子元素沿着 Z 轴放大四倍,X 轴和 Y 轴比例不变,观察下效果:
大家可以发现, Z 轴长度变为了之前的 4 倍。 上面我们是用 matrix3d 实现的,使用 scale3d 同样能达到上述效果,大家不妨试一试。
3D 旋转是基本变换最复杂的一个,涉及到绕基本坐标轴的旋转、绕任意轴的旋转、欧拉角旋转、四元数旋转等,但是 CSS 中除了 matrix 和 matrix3d 以外,只提供了绕基本轴旋转、绕任意轴旋转,欧拉角旋转和四元数旋转需要通过数学库计算出对应的旋转矩阵。我们还是先看下如何使用 matrix3d 实现基本旋转。
绕 X 轴旋转 θ 角度对应的变换矩阵各个元素的位置如下图所示:
举个例子,假设我们实现绕 X 轴旋转 45 度的效果,那么对应的 css 表示如下:
transform: matrix3d(
1, 0, 0, 0,
0, 0.7071, 0.7071, 0,
0, -0.7071, 0.7071, 0,
0, 0, 0, 1
);等价于
transform: rotateX(45deg);
transform: rotate3d(1, 0, 0, 45deg);效果如下图:
绕 Y 轴旋转 θ 角度对应的变换矩阵各个元素的位置如下图所示:
举个例子,假设我们实现绕 Y 轴旋转 45 度的效果,那么对应的 css 表示如下:
transform: matrix3d(
0.7071, 0, 0.7071, 0,
0, 1, 0, 0,
-0.7071, 0, 0.7071, 0,
0, 0, 0, 1
);等价于
transform: rotateY(45deg);
transform: rotate3d(0, 1, 0, 45deg);效果如下图:
绕 Z 轴旋转 θ 角度对应的变换矩阵各个元素的位置如下图所示:
举个例子,假设我们实现绕 Z 轴旋转 45 度的效果,那么对应的 css 表示如下:
transform: matrix3d(
0.7071, -0.7071, 0, 0,
0.7071, 0.7071, 0, 0,
0, 0, 1, 0,
0, 0, 0, 1
);等价于
transform: rotateZ(45deg);
transform: rotate3d(0, 0, 1, 45deg);效果如下图:
以上就是使用 matrix 和 matrix3d 来代替基本变换属性的讲解内容。大家应该能明白它们的用法了,归纳起来如下:
很简单。
那另一个问题是,如何求得变换矩阵呢?这就需要用到我们前面总结的数学矩阵库了。
对于基本变换,我们没有必要使用 matrix 来实现,但是对于一些基本变换满足不了的效果,我们就需要考虑 matrix 了。
比如,我需要为一个 dom 元素进行如下变换:
以上变换基于静态坐标系(世界坐标系)进行的。
对于这么一个复杂的变换,我们有两种方式来实现:
首先,我们看下如何使用组合变换来实现:
transform: rotateY(90deg)
rotateZ(45deg)
translateY(30px)
rotateX(45deg);在 css transform 章节中介绍了 transform 组合变换时各个变换的顺序与坐标系的关系,如果大家还没忘记的话,应该记得变换属性从后往前排列代表按照世界坐标系进行变换。
变换的分步执行过程如上图。
你会发现,如果变换过程比较多的话,transform 中要书写很多属性,所以可以尝试另一种思路,matrix3d。
我们看下如何使用 matrix3d 来表示。
利用之前的矩阵库,我们需要如下几个方法计算最终的变换矩阵:
你可能没有发现矩阵相乘的方法,这是因为 rotateZ、rotateX、translate 方法允许传入一个矩阵作为左乘矩阵,返回一个组合矩阵。当然,你也可以用矩阵相乘方法来实现组合矩阵的求值,但是那样会多执行一些重复运算。
这几个方法在之前章节已经讲述过,所以我们拿来使用即可:
var deg = Math.PI / 180;
// 首先创建一个沿 Y 轴旋转矩阵。
var target = matrix.rotationY(90 * deg);
// 接着计算 Y 轴旋转矩阵与 Z 轴旋转矩阵的组合变换。
// 等价于 matrix.multiply(target, matrix.rotationZ(45 * deg));
target = matrix.rotateZ(target, 45 * deg);
// 等价于 matrix.multiply(target, matrix.translate(0, 30, 0));
target = matrix.translate(target, 0, 30, 0, target);
// 等价于 matrix.multiply(target, matrix.rotationX(45 * deg));
target = matrix.rotateX(target, 45 * deg, target);我们将 target 打印出来看一下:
[ 4.329780632585522e-17, 0.7071067690849304, -0.7071067690849304, 0, 0.7071067690849304, 0.5, 0.5, 0, 0.7071067690849304, -0.5, -0.5, 0, -1.298934236097771e-15, 21.21320343017578, 21.21320343017578, 1]target 就是上面几种变换组合后的最终矩阵,我们将它转化为 css 的 matrix3d 属性:
// 将矩阵转化为transform matrix 属性值。
function matrix2css(m){
var style = 'matrix(';
if(m.length == 16){
style = 'matrix3d(';
}
for(let i =0; i< m.length; i++){
style += m[i];
if(i !== m.length - 1){
style += ',';
}else{
style += ')';
}
}
return style;
}最终效果如下:
很显然,通过组合变换和 matrix,我们达到了同样的效果。
通过比较上面的两种实现,我们发现,组合变换相较于 matrix 更容易理解,当变换次数较少时,使用组合变换更优一些。但是当变换次数越来越多时,就需要使用 matrix 了。那么,什么时候变换次数会越来越多呢?
下面,我会通过一个魔方格子的旋转来讲述 matrix3d 的使用场景。
大家都玩过魔方吧,沿不同方向进行旋转,可以将一个面的格子绕指定轴旋转 90 度。
接下来我们构造一个魔方格子,演示一下如何通过 matrix3d 完成这种交互。
首先,我们要构造一个魔方格子,魔方格子是一个立方体,在前面章节我曾经介绍过图片盒的实现,此处仍然采用图片盒的实现方式,只不过将图片换成 DIV 容器。
<div class="block" id="block">
<div class="face front"></div>
<div class="face back"></div>
<div class="face up"></div>
<div class="face down"></div>
<div class="face left"></div>
<div class="face right"></div>
</div>样式如下:
.block {
position: absolute;
transform-style: preserve-3d;
width: 100px;
height: 100px;
transform-origin: 50px 50px;
}
.front {
background: fuchsia;
}
.back {
transform: translate3d(0, 0, 100px) rotateY(180deg);
background: red;
}
.left {
transform-origin: 100% 50% 0px;
transform: rotateY(90deg);
background: aqua;
}
.right {
transform-origin: 0% 50% 0px;
transform: rotateY(-90deg);
background: blueviolet;
}
.up {
transform-origin: 50% 0% 0px;
transform: rotateX(90deg);
background: darkorange;
}
.down {
transform-origin: 50% 100% 0px;
transform: rotateX(-90deg);
background: darkviolet;
}为了便于观察,我们为让魔方格子旋转起来:
@keyframes rotate {
0% {
transform: translate(-50%, -50%) rotate3d(1, 1, 1, 0deg);
}
100% {
transform: translate(-50%, -50%) rotate3d(1, 1, 1, 360deg);
}
}
.block{
animation: rotate 3s linear infinite;
}魔方格子效果如下:
上图魔方格子的旋转是通过CSS 中的 animation 属性 来实现的,并没有通过交互来触发,接下来,我们讲解一下如何通过鼠标滑动进行旋转。
鼠标滑动分为左、右、上、下滑动,每种滑动对应一种方向的格子旋转。
当然旋转需要有一个参照点,默认盒子中心。在上一章《四元数的应用:使用鼠标控制模型的旋转》中我们使用四元数、欧拉角分别实现了模型的旋转交互,本节依然采用旋转矩阵的生成原理,区别就是将生成的矩阵转化为 CSS 中 transform 的 matrix3d 属性值。
var currentQ = {x:0, y:0, z:0, w:1};
var lastQ = {x:0, y:0, z:0, w:1};
var currentMatrix = matrix.identity();
var l = Math.sqrt(dx * dx + dy * dy);
if(l <= 0)return;
var x = dy / l, y = dx / l;
var axis = {x: x, y: y, z: 0};
var q = matrix.fromAxisAndAngle(axis, l);
currentQ = matrix.multiplyQuaternions(q, lastQ);
currentMatrix = matrix.makeRotationFromQuaternion(currentQ);通过上述方式我们计算出了当前旋转矩阵 currentMatrix,接下来,我们使用上面介绍的矩阵转化成对应 css 的函数,生成对应的 transform 属性。
var style = matrix2css(currentMatrix);最后将生成的样式应用到魔方格子上。
document.querySelector('#block').style.transform = style;至此,我们通过 matrix3d 实现了使用鼠标控制魔方格子旋转的效果。
以上就是数学矩阵库与 CSS transform 属性的高级使用技巧,通过这两个例子的学习,大家应该对 transform 的 3D 属性更有信心了,希望大家以后碰到交互给出的 3D 特效时不再畏手畏脚,而是大胆的拥抱它。
至此,我们对 CSS 的 3D 属性的学习就告一段落了。下一节,我们还是回到 WebGL 的学习内容上来。