在你未尝试之前,永远不要认为它很难,一起来看看 CSS 3D 能实现那些酷炫的东西吧

3D 是 CSS 范畴的吗?

在印象当中,web 实现 3D 第一时间会联想到 three.js 或者原生的 canvas WebGL,后来看到一些比较酷炫的 CSS 动画才意识到原来 CSS3 中早已经能实现简单的 3D 效果了,接下来,我将以实现一个 3D 场景下的正方体到全景漫游图为目标,具体讲解几个实现 CSS 3D 重要属性。

实现的效果

image

在线预览: css-pano
源码: web-pano

两个重要的属性

perspective

概念

perspective 指 3D 场景下透视距离,要理解这个属性,首先需要将平面的屏幕想像成一个 3D 的场景,假设我们距离屏幕 200px ,此时perspective即为200px,这个时候你在屏幕上绘制一个 50px 长宽的正方形,假设该正方体沿着 Z 轴移动 +100px,也就是向你面前靠近 100px,此时,根据“近大远小”的视觉效果,该正方形会变得越来越大,如果该正方体沿着 Z 轴移动 +199px,也就是移动到了你最靠近眼睛的位置,此时你将被一个正方形平面给完全盖住,眼前所有的东西都只是一个正方体,再进一步,如果该正方体沿着 Z 轴移动 +201px,此时,正方形将越过你眼睛,在你眼睛后面,你将不会在看到该正方形。perspective 就是这么简单的概念,我们看一段简单的代码,来实践上述所描述的场景。

代码效果

这里我们使用一个动画,沿着Z轴不断移动,直到越过你眼睛。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
<body>
<div class="card">
card
</div>
</body>

body {
height: 100vh;
display: flex;
justify-content: center;
align-items: center;
/* 透视距离 */
perspective: 200px;
}

@keyframes rotate {
0% {
transform: translateZ(0px);
}

100% {
transform: translateZ(200px);
}
}

.card {
width: 100px;
height: 100px;
background: green;
/* 空间移动距离 */
transform: translateZ(0px);
animation-duration: 2s;
animation-timing-function: linear;
animation-name:rotate;
}

image

生效范围

perspective 生效范围是设置该属性容器内的子元素, 它可以设置在任意容器上,假设我们只需要在一个小场景下实现 3D 效果,比如我们设置在一个宽高固定的容器上,当容器内效果超出容器长宽后,需要设置 overflow: hidden; 才能隐藏超出的效果,如下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
<body>
<div class="container">
<div class="card">
card
</div>
</div>
</body>
body {
height: 100vh;
display: flex;
justify-content: center;
align-items: center;
}

@keyframes rotate {
0% {
transform: translateZ(0px);
}

100% {
transform: translateZ(300px);
}
}

.container {
width: 300px;
height: 300px;
display: flex;
justify-content: center;
align-items: center;
/* 透视距离 */
perspective: 200px;
border: 1px solid red;
/* 超出即隐藏 */
overflow: hidden;
}

.card {
width: 100px;
height: 100px;
background: green;
/* 空间移动距离 */
transform: translateZ(0px);
animation-duration: 2s;
animation-timing-function: linear;
animation-name:rotate;
}

image

perspective-origin

perspective-origin,透视视角,该属性是表示“眼睛处在容器的哪个方位”,默认值是 perspective-origin: 50% 50%;,即容器的中间,也就是为什么上面两个3D效果都是朝你眼睛正中扑面而来,我们多设置几个“视角”来体验该属性的效果:

如下三个视角分别是:

1
2
3
4
5
6
7
8
9
10
.container1 {
perspective-origin: 50% 50%;
}
.container2 {
perspective-origin: 0 0;
}

.container3 {
perspective-origin: 50% 0;
}

image

transform-style

transform-style 指定子元素如何在3D空间中呈现,有两个属性值:flat和preserve-3d,其中 flat 值为默认值,表示所有子元素在2D平面呈现。preserve-3d 表示所有子元素在3D空间中呈现,简单来讲 flat 场景下,只有 x,y 轴的效果,而 preserve-3d 具有 x,y,z 三轴方向,比如两个平面在空间里相交,在 flat 只有覆盖效果,而 preserve-3d 具有真实的前后相交的效果,我们直接看代码效果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
<div class="container">
<div class="card card1">card1</div>
<div class="card card2">card2</div>
</div>

.container {
position: relative;
height: 300px;
width: 300px;
border: 1px solid red;
/* transform-style: preserve-3d; */
perspective: 200px;
}

.card {
height: 160px;
width: 240px;
position: absolute;
left: 0;
}

.card1 {
background: red;
}

.card2 {
width: 290px;
background: green;
transform: rotateX(40deg);
}
  • transform-style: flat:

image

  • transform-style: preserve-3d:

image

实现一个正方体

在理解上述两个重要属性后,我们开始实现一个正方体,拼一个正方体需要6个平面,分别将其在 3D 场景下放置到合理位置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
<div class="box">
<div class="scene">
<div class="cube">
<div class="side-back slide"></div>
<div class="side-front slide"></div>
<div class="side-left slide"></div>
<div class="side-right slide"></div>
<div class="side-top slide"></div>
<div class="side-bottom slide"></div>
</div>
</div>
</div>

.box {
perspective: 90px;
height: 300px;
width: 600px;
overflow: hidden;
border: 1px solid red;
}
@keyframes rotate {
0% {
transform:translateZ(-400px) rotateY(0deg) ;
}
100% {
transform:translateZ(-400px) rotateY(360deg);
}
}
.scene {
display: flex;
justify-content: center;
align-items: center;
width: 100%;
height: 100%;
transform-style: preserve-3d;
}

.cube {
position: absolute;
width: 400px;
height: 400px;
transform-style: preserve-3d;
transform-origin: center center;
transform: translateZ(-400px) rotateY(0);
animation: rotate 12s linear infinite;
}

.slide {
position: absolute;
width: 100%;
height: 100%;
}

.side-front {
transform: rotateY(0) translateZ(-200px);
background: yellow;
}

.side-back {
transform: rotateY(180deg) translateZ(-200px);
background: skyblue;
}

.side-left {
transform: rotateY(90deg) translateZ(-200px);
background: green;
}

.side-right {
transform: rotateY(-90deg) translateZ(-200px);
background: blue;
}

.side-top {
transform: rotateX(-90deg) translateZ(-200px);
background: rebeccapurple;
}
.side-bottom {
transform: rotateX(90deg) translateZ(-200px);
background: salmon;
}

效果:

image

使得视角处在正方体内部

假设我想看到正方体内部的 3D 效果,我们可以将该容器沿着 Z 轴向我们视角靠近,我们初始的透视距离是 90px,正方体的长宽高是 400px,我们想处在正方体中间,那么就需要将正方体容器沿着 Z 轴移动 +290px:

1
2
3
4
5
6
7
8
9
.scene {
display: flex;
justify-content: center;
align-items: center;
width: 100%;
height: 100%;
transform-style: preserve-3d;
transform: translateZ(290px);
}

效果:

image

全景图实现

假设每一块平面是真实的图像场景,我们使用特殊的相机,或者使用渲染引擎渲染出整个前后左右上下的场景,在中心点,向六个面分别拍摄一张照片,将其贴入正方体内部的六个面,那么就能以假乱真地渲染出 3D 全景图的效果,我们试一试:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
...
.scene {
...
/* 调整到最佳位置 */
transform: translateZ(480px);
}

...
.side-front {
transform: rotateY(0) translateZ(-200px);
/* background: yellow; */
background-image: url("https://qhyxpicoss.kujiale.com/r/2019/07/10/L3D224S3ENDIA5FTCIUI5N2GLUF3P3WC888.0_12000x2000.jpg_f?x-oss-process=image/resize,m_fill,w_1024,h_1024");
}
.side-front {
transform: rotateY(0) translateZ(-200px);
/* background: yellow; */
background-image: url("http://static.vince.xin/UYCFVGBHNJMK.jpg");
}
.side-back {
transform: rotateY(180deg) translateZ(-200px);
/* background: skyblue; */
background-image: url("http://static.vince.xin/FRTGYHUJKBHNJ.jpg");
}
.side-left {
transform: rotateY(90deg) translateZ(-200px);
/* background: green; */
background-image: url("http://static.vince.xin/YGVGBHJNKM.jpg");
}
.side-right {
transform: rotateY(-90deg) translateZ(-200px);
/* background: blue; */
background-image: url("http://static.vince.xin/YJMEDFGHJKIUH.jpg");
}
.side-top {
transform: rotateX(-90deg) translateZ(-200px);
/* background: rebeccapurple; */
background-image: url("http://static.vince.xin/YTGHJKLSDF.jpg");
}
.side-bottom {
transform: rotateX(90deg) translateZ(-200px);
/* background: salmon; */
background-image: url("http://static.vince.xin/YOKSDCVBNM.jpg");
}

效果:

image

这里主要的难点在于,如何找到最佳的 perspective 透视距离和容器移动的 Z 轴距离,其他都是比较简单的 CSS 3D 移动。

总结

总的来说,CSS 中的 3D 效果还是比较简单的,我们理解了上面两个重要属性后,将元素在 X,Y,Z 轴移动结合,就能实现很多酷炫的效果,想要熟练地掌握需要我们多试错与实践,Thanks~

refs