现在是0202年,距CSS3开始制定已过去了21年。身为公司排名第17的员工,觉得有必要学一学CSS。现在CSS的功能可以说是相当丰富了,可以做出各种让人收不住下巴的酷炫效果,比如这个网站。不过,在使用CSS的过程中,我遇到了一些问题。比如,当使用CSS rotate、rotateX的时候,在传入的参数值完全相同的情况下,仅仅是顺序不同,就会得到截然不同的结果。而就在我查看CSS文档以求知道为什么会得到截然不同的结果时,CSS说的坐标系变换又让我看得云里雾里。经过一番探索,我才终于明白为什么rotate顺序不同时结果不同以及CSS说的坐标系变换和点坐标变换之间的关系。
从CSS的transfrom说起
为了照顾不经常接触CSS变换效果的WEB开发者,我觉得有必要先讲讲CSS的transform是什么。就我对其粗浅的了解,CSS的transform是提供了一些让元素产生仿射变换的接口,比如平移、旋转、缩放等。而w3c的解释是,tranform通过transform属性将变换应用于元素渲染到的坐标系(这句啥意思?后面会讲→_→)。你现在明白tranform是啥了吧,不明白也不要紧,俗话说一例胜千言,下面让我们来分别看一下用tranform属性实现对元素的平移、旋转和缩放效果。
上图的代码在这儿。
三维变换
上面所说的只是二维变换,下面聊聊三维变换。三维变换在二维变换的基础上加上了z轴。各轴的关系看下图。
(图片转自吴锴的博客)
其中,x轴在屏幕上的方向是向右,y轴是向下,而z轴是垂直于屏幕向外。三维变换中的translate3d和scale3d都挺好理解的,就是rotate的旋转方向有些傻傻分不清楚。我在网上搜索了一下,加上自己实验,得出结论:rotateX、rotateY和rotateZ旋转是眼睛方向和负轴方向一致,顺时针旋转。而rotate3d则是眼睛方向与所指定向量负方向一致,顺时针旋转。
知道旋转方向就算理解tranform的rotate了吗?不,我们的探索之旅才刚刚开始。
rotate顺序不同
下面我们来看一个例子。
代码(要看完整代码请点这儿):
.box0 {
transform: perspective(200px) none;
}
.box1 {
transform: perspective(200px) rotateX(45deg) rotate(90deg);
}
.box2 {
transform: perspective(200px) rotate(90deg) rotateX(45deg);
}
效果:
从代码中,我们看到,传给rotate变换的值是一样的,只是rotateX和rotate的顺序不同。而从图中,我们看到,不同rotate顺序所得到的动画效果差别很大。为什么会这样呢?其实自己在脑中模拟一下三维旋转就能知道,rotate顺序不同时得到的结果是不同的。这应该可以说是三维旋转的一个性质。更进一步地说,三维旋转没有交换律。
我在知道了三维旋转没有交换律后,还是搞不懂为什么上面的box1是那样的“体位”。不应该是图形先绕X轴旋转45度,然后再绕Z轴旋转90度吗?它难道不应该是和上图中box2一样吗?经过我的试验,我发现,transform的三维变换是从右往左进行的。也就是说,box1先绕Z轴旋转90度,再绕X轴旋转45度。然而这就是答案了吗?不是的。W3C说,我们旋转的是坐标系(顺序是从左往右)。仔细想想,你会发现,它确实是能得到和上图相同的结果。
那么这两者为什么会得到相同的结果呢?
认识变换矩阵
我们应该都知道矩阵是什么,那么线性变换矩阵是什么呢?要完整解释需要很长的篇幅,我们这里长话短说,主要关注三维变换相关的部分。(我们在这里假设读者已经熟悉矩阵、矩阵乘法以及矩阵的各种常见性质,不懂的读者可以用各种姿势查找资料学习。)
三维变换中的平移、缩放和旋转都属于仿射变换。仿射变换就是线性变换加平移变换,一个对向量
当
当
对点做旋转变换也可以类似地得到,这里就不提了。
以前接触过CSS三维变换矩阵的同学可能要问,CSS transform里函数对应的矩阵不是四维的吗,这里的三维矩阵和它有什么关系。当然是有关系的啦。其实它们是等价的。考虑下面的式子:
其中,
我们得到的
综上,我们知道三维坐标变换可以由矩阵来表示。
坐标系表示
在前面我们讲了矩阵变换可以用来表示一个点的三维变换,那么有没有办法用矩阵来表示一个坐标系呢?毕竟W3C说的是对坐标系进行变换。
首先,我们要明白线性空间的基是什么。(写到这里,我翻出了珍藏的大学《线性代数(陈维新版)》书,上面写道:)
设是数域上的一个线性空间,中满足下列条件的向量组称为的一组基。
(1)线性无关;
(2)中每一个向量都可经线性表示。
线性表示是啥意思呢?如果
线性无关是啥意思呢?更准确的定义是,任意
为什么要了解基是什么呢? 因为一组基加一个原点,可以描述一个坐标系。而单个基加原点,可以看作是表示了一个坐标轴。
假设原点坐标为
线性变换
线性变换是线性空间的变换(线性空间到它自己的映射)。整个空间所有点都绕某向量旋转这种变换,就是一个线性变换。这个旋转的例子中,空间中的每个点都绕那个向量旋转了一个角度,平移前的点映射到平移后的点,不过点所在的空间还是没变的,还是那个线性空间,所以这叫做线性空间到它自己的映射。
我们假设
用矩阵形式来表示就是:
令
坐标系变换与点坐标变换
(注:下文中的基础坐标系就是我们常见的直角坐标系,它的基分别为
前面有说过,W3C说transform里的变换是针对坐标系的,其实这并不准确。事实上,transform里的变换是对整个空间(也就是空间上的所有点)进行变换,而对整个空间进行平移、旋转和缩放变换,也就是仿射变换。换句话说,transform里的平移、旋转和缩放,就是一个个仿射变换。
在上面“坐标系表示”这一节中,我提到,坐标系可以由基和原点组成的矩阵来表示。设
要弄懂这个结果,我们需要弄清楚等号右边的
我们可以设
设要求解的
写到这里,相信聪明的读者已经发现了,
这下我们算是知道怎么对坐标系进行变换了。那点的变换和坐标系的变换有什么关系呢?
直观点的想法,已经知道变换后的坐标系以及点在这个坐标系上的坐标值,那么我们就可以求得点在基础坐标系上的坐标值。
设变换后的坐标系下的点
联解上面四个式子,并结合基线性无关的性质,得:
这样一来,我们就可以用 给定的使坐标系发生变换的矩阵 来求某点在初始坐标系(一般是基础坐标系)下的坐标了。
另外,类似地,我们可以推出下面的结论:
其中,
这个结论告诉我们,对坐标系进行变换,可以转换成对点的坐标进行变换。
transform里的变换
在CSS中,初始的坐标系是基础坐标系,基础坐标系对应的矩阵就是单位矩阵,如下:
而transform里的每个函数,就对应一个变换矩阵。拿平移来举例,translate3d(tx, ty, tz)对应下面的矩阵:
在transform-origin为原点时,浏览器会将所有的变换矩阵都乘起来,然后再用矩阵乘法乘出来的结果来求出图形上每个点的位置。这也就解释了为什么前面三维旋转没有交换律,因为矩阵乘法没有交换律。
为什么W3C说这是在对坐标系进行变换呢?看了前一节的读者应该能明白。这是因为标准里是从一个单位矩阵(也就是基础坐标系对应的矩阵)开始进行计算,而因为矩阵乘法有结合律,所以虽然它是先把所有的变换矩阵做矩阵乘法,我们还是可以把它看成是单位矩阵按顺序一个一个的去乘那些变换矩阵。去乘这些变换矩阵,就相当于是在对坐标系进行变换。
顺序相反的rotate
现在还剩最后一个问题,为什么我们按从右往左的顺序旋转图形,会得到对坐标系从左到右进行旋转变换相同的结果。
首先,我们得理清一个概念,旋转图形到底是在做什么变换。是线性变换吗?不是,因为每次的参考坐标系都是基础坐标系,是没变的。旋转图形实际上是在做点的坐标变换。而根据上面坐标系变换和点变换可以互相转化的结论,点坐标变换是和坐标系变换的顺序是相反的。
总结
本文首先简单介绍了一下CSS的transform以及transform中rotate的旋转方向的问题。然后本文提出用一个矩阵来表示一个三维坐标系,通过这样的方式来解释CSS说的对坐标系进行变换,并且证明了在初始坐标系的坐标轴对应的向量线性无关的情况下,坐标系变换和点变化可以相互转化且顺序相反。相信大家在看了本文后,应该对CSS的三维变换有了一个更加清晰的认识。
Makeflow (makeflow.com) 是以流程为核心的项目管理工具,让研发团队能更容易地落地和改善工作流,提升任务流转体验及效率。如果你正为了研发流程停留在口头讨论、无法落实而烦恼,Makeflow 或许是一个可以尝试的选项。如果你认为 Makeflow 缺少了某些必要的特性,或者有任何建议反馈,可以通过GitHub、语雀或页面客服与我们建立连接。