CSS 3D 空间
涉及属性
perspectiveperspective-origintransform-styletransform ( rotateX, rotateY, translateZ )transform-origin
必要条件
要实现 3D 效果必须要先理解 3D 空间是怎么形成的,以及实现 3D 效果的必要条件是什么。
Z 轴
区别与 2D 场景,3D 场景与 2D 场景最大的区别是有了 Z 轴,那么 Z 轴在 css 中该如何产生呢?
这就涉及到上面列出的第一个属性: perspective(景深) MDN。
首先我们必须要知道 Z 轴如果生成了会有什么效果,最直观的感受就是元素在 Z 轴 上面平移会有大小变化。
我们都说空间是由一个个平面构成,也就是说如果说我们规定一个与元素平行的平面,以这个平面为基础去定义元素平面的 Z轴 那么 3D 空间就产生了。这也是 perspective 的作用所在,因此 perspective 属性可以认为是开启 3D 场景。
perspective 规定了观察者距离垂直于元素 2D 平面下的距离。
简单的例子就是一张纸我们可以理解为一个简单的元素,纸面就是 2D 空间的平面,如果设定了 perspective: 100px; 就相当于你拿起了这张纸(纸面与你的面平行),那么这张纸和你这个人就够成了 3D 空间。
总结来说,3D 空间的构成必须要有一个参照平面,两个不同的平面才能照成视差,也就产生了 3D 的效果,而 perspective 就规定了这两个参照平面的距离。
需要注意的是:
当一个元素有了 perspective 属性后,元素的大小并不会发生变化。
所以当一个元素的 css 为
perspective: 100px;
width:100px;
height:100px;
可以理解为,在距离观察平面 100px 处有一个看上去是 100X100 的元素,注意是看上去,这和拍照是同一个道理,设置的大小就是照片拍出来后在照片中的大小。
perspective 仅仅支持正值,负值或是 0 不生效。
faq: 针对于网上一些文章中发现的问题
-  设置了 
perspective属性就相当于这距离元素多远处放置一只眼睛然后观察元素 这样的描述不太对,一只眼睛相当于一个点,而不是一个平面,这不仅仅确定了Z轴同时还确定了X与Y轴,但是产生的效果确实和这样的描述相符合。原因是因为,默认的transform变换点为元素的中心,这点不用设置perspective就可以确定,所以如果设置了perspective-origin这样的描述就不对了。所以可以这么认为 眼睛的位置由perspective-origin确定,而这只眼睛距离元素多远由perspective属性确定。 -  设置 
perspective属性就相当于产生了垂直于屏幕平面的Z轴。 这个描述也不对,仅仅特定的情况下生效,那就是元素的父元素没有3D变换,或是有3D变换但没有旋转X或Y轴。原因如下:一个元素在没有3D空间的情况下是永远和屏幕在同一个平面的,所以垂直于屏幕等效于垂直于元素,但是如果说元素的父元素处在3D空间内,并且进行了X轴 或Y轴 的旋转,那么这个观察平面是不会和屏幕平行,那么变换所依据的Z轴 也不会垂直于电脑平面。 
transform 变换
有了 3D 空间,如果不进行变换的话是没有任何效果的,所以产生 3D 效果的第二个条件就是变换。
变换基于 3D 坐标轴,所以使用 transform 变换最重要的是确定 3D 坐标轴。
那么如何确定 3D 坐标轴?
很简单,我们找出坐标原点即可,就是 transform-origin MDN的值,默认值为 transform-origin: 50% 50% 0; 也就是元素的正中心。
需要注意的是如果没有 3D 空间,transform-origin 的第三个值是无效的,因为对于元素来说,它并不知道观察点距离自己多远,也就进行不了视差的变化。
faq: 必须要理解的几个问题
-  
transform到底在改变什么?transform改变的是坐标轴,而不是元素,元素的呈现是随便坐标轴的变化而变化的,比如transform: scale(.5);就是坐标轴缩小了,那么对应元素的显示就缩小了,并不是元素的大小缩小了,而坐标轴没变,元素的真实大小永远不会变,变的是元素在不同坐标轴下的呈现。 当然除非修改transform-origin,坐标轴对于元素来说永远不会变。理解了这个那么对于transform的应用应该也没有问题。 -  不同的元素是否在同一个坐标轴下? 不是。每个元素都有自己坐标轴,即使是父子元素也是处在不同的坐标轴下,但是子元素会受父元素影响,子元素的起始坐标轴和父元素的最终坐标轴一致。举个例子,当父元素的 X 轴旋转了 
45deg,那么子元素坐标轴的起始位置就已经旋转了45deg。 
3D 效果的叠加
在上一节的 faq 中的第二点,如果实验的话,应该是有点小问题的,因为在浏览器中,一个元素的内容默认是以 2D 效果呈现的,也就是 transform-style: flat; 。关于这个属性我们来具体的实验一下,结果通过实验来获得。
实现的前提条件是:有一个 3 层嵌套的 div ,在最外层的 div 上设置 perspective 生成 3D 空间,最深一级的 div 上添加旋转,代码大致如下
<div class="box1">
    <div class="box2">
        <div class="box3"></div>
    </div>
</div>
.box1 {
    perspective: 300px;
}
.box3 {
    transform: rotateY(45deg);
}
接着我们来控制 box2
box2无变换,不添加transform-style: preserve-3d
点击查看:jsfiddle。
可以发现 box3 处在 box2 的 3D 空间中,box2 的背景成了空间的背景。
box2无变换,添加了transform-style: preserve-3d
点击查看:jsfiddle。
可以发现 box2 成了 3D 变换中的一员,由于 box3 的旋转,导致有一部分旋转到了 box2 的后面,结果就是看不见了。
换句话说 box2 在 box1 的 3D 空间中,box3 也在 box1 的 3D 空间。
box2有变换,不添加transform-style: preserve-3d
点击查看:jsfiddle。
可以发现 box2 处在 3D 空间中,而 box3 处在 box2 的平面里。
box2有变换,添加了transform-style: preserve-3d
点击查看:jsfiddle。
结果和情况 2 呈现的效果一致,两个元素都处在 box1 的 3D 空间里。
分析:
- 前面我们说过:
perspective会开启一个3D空间,确定了其子元素的Z轴,因此box2是能做3D变换的。所以34情况中box2的状态我们可以确定,就是这样呈现的。 34情况中的区别,在于box3的呈现,而代码上的区别在于box2有没有添加transform-style: preserve-3d这条属性,那么这条属性的效果就是将子元素放在3D空间中显示,这条属性的另一个值flat也就是默认值就是将子元素压平在父元素中显示,就好像往父元素中塞了一张照片。- 对比 
24发现,在添加了transform-style: preserve-3d的情况下,父元素的变换并不会影响到子元素。但对比12发现,不添加transform-style: preserve-3d时,事情有点出乎意料。box2成了变换的3D空间,但是box2上却没有perspective属性。 - 对于第 
3点,我们是不是可以认为当子元素没有进行变换的时候,3D空间是会被赋给子元素??这点不太确定,表现如此,但也找不到相关的内容,有待查证。 
总结
perspective规定了一个与元素平行的平面,生成了3D空间,与perspective-origin配合确定了视觉的观察点。transform-origin规定了一个元素的3D坐标原点,前提元素处在3D空间内。transform可以将元素的坐标轴进行变换,不直接修改元素,元素只是对于坐标轴的呈现。transform-stylepreserve-3d: 将子元素以3D效果呈现。flat: 将子元素沿着父元素的Z轴压扁,效果和拍照一致。- 当祖父元素开启了 
3D空间,而父元素没有进行变换,3D空间会延续到父元素上。 - 最后一点有待考证。
 
记在最后
如果在一个拥有 perspective 属性的元素内的子元素上在使用 perspective 如下的形式
<div class="box1">
    <div class="box2">
        <div class="box3"></div>
    </div>
</div>
.box1 {
    perspective: 100px;
}
.box2 {
    perspective: 100px;
}
那么 box3 的 Z 轴相关的变换就会发生变化,实验下来会被拉伸,以 translateZ() 为例,以下为测试内容。
测试数据基于
<div class="box1">
    <div class="box2">
        <div class="box3"></div>
    </div>
</div>
.box1 {
    width: 500px;
    height: 500px;
    position: relative;
    perspective: 100px;
}
.box2 {
    width: 100px;
    height: 100px;
    position: absolute;
    top: 200px;
    left: 200px;
    background: rgba(0, 0, 0, 0.3);
    transform-style: preserve-3d;
    perspective: 100px;
}
.box3 {
    width: 100px;
    height: 100px;
    background: blue;
    transform: translateZ(-200px);
    transform-style: preserve-3d;
}
我们根据数据来计算 box3 距离 box1 3D 空间观察点的距离(假设为 X):
计算方法为: (box3 呈现大小)/(box3 设置尺寸) = (box1 perspective)/(X)
根据 box3 与 box1 3D 空间观察点的距离,我们可以得出 box3 在 box1 空间内的 Z 轴偏移量。
计算方法为: (box1 perspective) - X
观察下表可以看出 box3 呈现出来的 Z 轴偏移量和设置的 translateZ 并不相等,将 7 组数据总结了之后,可以得出
公式: (box3 的呈现偏移量) = ((box1 perspective)/(box2 perspective) + 1) * (box3 translateZ)
公式所呈现的效果就是把 box3 的坐标轴沿着 Z 轴进行了拉伸,拉伸的倍数为 ((box1 perspective)/(box2 perspective) + 1)。
公式所代表的意思,暂时无法理解。但是效果就是如果叠加使用 perspective 那么第二层设置了 perspective 属性的元素中的子元素的 Z 轴会进行拉伸。
| 含义 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 
|---|---|---|---|---|---|---|---|
| box1 perspective | 100 | 100 | 100 | 100 | 100 | 200 | 300 | 
| box2 perspective | 100 | 100 | 100 | 200 | 300 | 100 | 100 | 
| box3 translateZ | -100 | -200 | -300 | -100 | -100 | -100 | -100 | 
| box3 呈现大小 | 33.33 | 20 | 14.29 | 40 | 42.86 | 40 | 42.86 | 
| box3 距离 box1 观察点的距离 | 300 | 500 | 700 | 250 | 233.3 | 500 | 700 | 
| box3 偏移量 | -200 | -400 | -600 | -150 | -133.3 | -300 | -400 |