CSS 3D 空间
涉及属性
perspective
perspective-origin
transform-style
transform ( 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
变换的。所以3
4
情况中box2
的状态我们可以确定,就是这样呈现的。 3
4
情况中的区别,在于box3
的呈现,而代码上的区别在于box2
有没有添加transform-style: preserve-3d
这条属性,那么这条属性的效果就是将子元素放在3D
空间中显示,这条属性的另一个值flat
也就是默认值就是将子元素压平在父元素中显示,就好像往父元素中塞了一张照片。- 对比
2
4
发现,在添加了transform-style: preserve-3d
的情况下,父元素的变换并不会影响到子元素。但对比1
2
发现,不添加transform-style: preserve-3d
时,事情有点出乎意料。box2
成了变换的3D
空间,但是box2
上却没有perspective
属性。 - 对于第
3
点,我们是不是可以认为当子元素没有进行变换的时候,3D
空间是会被赋给子元素??这点不太确定,表现如此,但也找不到相关的内容,有待查证。
总结
perspective
规定了一个与元素平行的平面,生成了3D
空间,与perspective-origin
配合确定了视觉的观察点。transform-origin
规定了一个元素的3D
坐标原点,前提元素处在3D
空间内。transform
可以将元素的坐标轴进行变换,不直接修改元素,元素只是对于坐标轴的呈现。transform-style
preserve-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 |