3D 转换与透视
熟悉 CSS 的 transform 样式属性之后,就可以进一步透过 perspective、perspective-origin、transform-style、transform-origin 和 backface-visibility 等样式属性,实作 3D 变形效果,这篇教学会介绍如何实作这些非常有趣的 3D 转换和透视。
请先阅读“transform 转换函数”。
快速导览:
定义 3D 场景与摄影机
如果要使用 transform 实作“3D”转换,则必须要定义和 3D 息息相关的“场景与摄影机”,在 3D 绘图领域里,都会透过特定的“摄影机”拍摄最后所呈现的结果,3D 绘图的摄影机和一般的相机一样都可以设定“焦距”,焦距越短发生的透视变形就越大,焦距越长透视变形就越小。
使用 CSS transform 产生 3D 转换采用 perspective 代替“焦距”呈现画面,perspective 在 W3C 的定义 中表示“相机和元素”的距离的距离,也可称为“透视距离”,以下图为例,d 为透视距离,Z 为元素本身在 Z 轴的位移,透过 d 和 Z 计算出最后元素呈现在网页中的长相。
由于网页中不存在“摄影机”元素,必须透网页结构的方式,产生类似摄影机元素,下方是建构 CSS 3D 效果常用的网页结构,将“要变成 3D”的元素放在类似“摄影机”的元素里,甚至可以额外使用一个“场景”元素放置所有 3D 的子元素,如此一来就可以透过摄影机、场景和元素的操作,实现 CSS 3D 效果。
<!-- 攝影機 + 元素 -->
<div class="camera">
<div>3D 元素</div>
</div>
<!-- 攝影機 + 場景 + 多個元素 -->
<div class="camera">
<div class="space">
<div>3D 元素</div>
<div>3D 元素</div>
<div>3D 元素</div>
</div>
</div>
perspective 透视距离
理解 CSS 的 3D 架构后,接着就要进行“摄影机”的相关设定,虽然 CSS 的转换没有办法设定“焦距”,但透过 perspective 仍然能模拟出不同焦距的“透视变形”,perspective 表示“透视距离”的样式属性,没有继承特性,通常会套用在“摄影机”元素,数值越小就等同焦距越短 ( 越广角、透视变形多 ),数值越大则等同焦距越长 ( 越望远,透视变形少 ),最小值为 1px,如果小于 1px 就会当作 1px,如果不设定数值则会使用默认值 none,表示从无限远观察,元素不会发生任何透视变形。
下方范例呈现四种不同的 perspective 效果。
<!-- HTML 程式碼-->
<div class="a camera"><div>oxxo</div></div>
<div class="b camera"><div>oxxo</div></div>
<div class="c camera"><div>oxxo</div></div>
<div class="d camera"><div>oxxo</div></div>
<!-- CSS 程式碼 -->
<style>
div {
width: 100px;
height: 100px;
border: 1px solid #000;
color: white;
}
div[class] {margin: 20px 150px;}
.camera div{transform: rotateX(45deg);}
.a div {background: #f90;}
.b {perspective: 200px;}
.b div {background: #09f;}
.c {perspective: 100px;}
.c div {background: #0a0;}
.d {perspective: 50px;}
.d div {background: #f55;}
</style>
perspective-origin 透视中心点
perspective-origin 表示“透视中心点”样式属性,也可以理解为“摄影机中心点”,具有 x 和 y 两个属性值,分别代表相对于父元素的 x 座标和 y 座标,0 0 表示位于父元素的左上角,默认值为 50% 50%,没有继承特性,也可使用下列的位置表示法:
| 位置关键字 | 等同数值 |
|---|---|
| left | 0、0% ( 水平方向 ) |
| right | 100% (水平方向 ) |
| center | 50% ( 水平或垂直方向 ) |
| top | 0、0% ( 垂直方向 ) |
| bottom | 100% (垂直方向 ) |
下方范例呈现不同 perspective-origin 对于透视的影响,可以注意当透视中心位于元素的背面,看到的文字会是相反的。
<!-- HTML 程式碼-->
<div class="a camera"><div>hello<br/>OxxO<br/>12345</div></div>
<div class="b camera"><div>hello<br/>OxxO<br/>12345</div></div>
<div class="c camera"><div>hello<br/>OxxO<br/>12345</div></div>
<div class="d camera"><div>hello<br/>OxxO<br/>12345</div></div>
<div class="e camera"><div>hello<br/>OxxO<br/>12345</div></div>
<div class="f camera"><div>hello<br/>OxxO<br/>12345</div></div>
<!-- CSS 程式碼 -->
<style>
div {
width: 100px;
height: 100px;
border: 1px solid #000;
color: white;
font-size: 24px;
}
div[class] {margin: 20px 150px;}
.camera {perspective: 100px;}
.camera div{transform: rotateX(90deg);}
.camera:nth-of-type(n+4) div{transform: rotateY(90deg);}
.a {perspective-origin: 50% 0;}
.a div {background: #f55;}
.b {perspective-origin: 50% 50%;}
.b div {background: #09f;}
.c {perspective-origin: 50% bottom;}
.c div {background: #0a0;}
.d {perspective-origin: left center;}
.d div {background: #f90;}
.e {perspective-origin: 50% 50%;}
.e div {background: #0aa;}
.f {perspective-origin: 100% 50%;}
.f div {background: #a0a;}
</style>
transform-style 变形转换模式
操作元素转换时,如果只有单一个元素往往很好处理,但如果有“多个元素”,就可能会发生“互相重叠”或“各自转换”等不合理的 3D 呈现方式,这时就能透过 transform-style 设定“变形转换模式”,让空间里的“子元素”彼此独立在各自的空间,或整合为同一个 3D 空间,这个样式属性没有继承特性,有下列两种属性值:
| 属性值 | 说明 |
|---|---|
| flat | 所有子元素位于各自的空间,使用原本元素 HTML z-index 排列 ( 默认值 )。 |
| preserve-3d | 所有子元素位于同一个 3D 空间,使用座标系统位置排列。 |
下方范例有两组 div,都使用“camera、space 和元素”的架构定义出摄影机、空间和元素,但第一组 div 因为没有设定 transform-style: preserve-3d,造成子元素各自独立在自己的空间中,转换后就发生不合理的 3D 现象,而第二组 div 因为将所有子元素整合到同一个 3D 空间,就会根据空间中的位置排列组合,变成合理的 3D 模样。
<!-- HTML 程式碼-->
<div class="camera">
<div>
<div class="a">oxxo</div>
<div class="b">oxxo</div>
<div class="c">oxxo</div>
</div>
</div>
<div class="camera">
<div class="space"> <!-- 有加上 space 類別 -->
<div class="a">oxxo</div>
<div class="b">oxxo</div>
<div class="c">oxxo</div>
</div>
</div>
<!-- CSS 程式碼 -->
<style>
div {
width: 100px;
height: 100px;
position: relative;
}
.camera {
perspective: 200px;
margin: 80px;
}
.space {transform-style: preserve-3d;}
.a, .b, .c{
position: absolute;
width: 100px;
height: 100px;
color: white;
}
.a {
transform: translateZ(10px);
background: #09f;
}
.b {
transform: rotateX(75deg);
background: #f55;
}
.c {
transform: rotateY(75deg);
background: #f90;
}
</style>
transform-origin 转换中心点
transform-origin 表示元素转换时的“中心点”,没有继承特性,具有“x y”或“x y z”两种写法,分别表示要参考的 xyz 三轴的位置,z 轴默认值为 0,xy 轴默认值为 50%,xy 轴也可使用下列的位置表示法 ( 不支援 z 轴 )
| 位置关键字 | 等同数值 |
|---|---|
| left | 0、0% ( 水平方向 ) |
| right | 100% (水平方向 ) |
| center | 50% ( 水平或垂直方向 ) |
| top | 0、0% ( 垂直方向 ) |
| bottom | 100% (垂直方向 ) |
下方范例呈现不同 transform-origin 的效果,可以注意旋转的位置,以及当透视中心位于元素的背面,看到的文字会是相反的。
<!-- HTML 程式碼-->
<div class="a camera"><div>hello<br/>OxxO<br/>12345</div></div>
<div class="b camera"><div>hello<br/>OxxO<br/>12345</div></div>
<div class="c camera"><div>hello<br/>OxxO<br/>12345</div></div>
<div class="d camera"><div>hello<br/>OxxO<br/>12345</div></div>
<div class="e camera"><div>hello<br/>OxxO<br/>12345</div></div>
<div class="f camera"><div>hello<br/>OxxO<br/>12345</div></div>
<!-- CSS 程式碼 -->
<style>
div {
width: 100px;
height: 100px;
border: 1px solid #000;
color: white;
font-size: 24px;
}
div[class]:first-child{margin: 100px 50px 30px;}
div[class] {margin: 30px 50px;}
.camera {perspective: 200px;}
.camera div{transform: rotateX(90deg);}
.camera:nth-of-type(n+4) div{transform: rotateY(90deg);}
.a div {
background: #f55;
transform-origin: 0 0 0; /* 也可單獨寫 0 0 */
}
.b div {
background: #09f;
transform-origin: 0 50% 0; /* 也可單獨寫 0 50% */
}
.c div {
background: #0a0;
transform-origin: 0 100% 0; /* 也可單獨寫 0 100% */
}
.d div {
background: #f90;
transform-origin: 0 0 0; /* 也可單獨寫 0 0 */
}
.e div {
background: #0aa;
transform-origin: 50% 0 0; /* 也可單獨寫 50% 0 */
}
.f div {
background: #a0a;
transform-origin: 100% 0 0; /* 也可單獨寫 100% */
}
</style>
backface-visibility 背面可视
backface-visibility 可以设定元素翻转后“是否可以看见背面”,没有继承特性,默认值为 visible 表示可以看见背面,设定为 hidden 表示看不见背面 ( 这个元素翻转成背面后就会消失 ),下方范例会用两组 div,呈现“看见背面”和“看不见背面”的效果。
<!-- HTML 程式碼-->
<div class="camera">
<div class="space">
<div class="a">oxxo<br>1</div>
<div class="b">oxxo<br>2</div>
<div class="c">oxxo<br>3</div>
<div class="d">oxxo<br>4</div>
<div class="e">oxxo<br>5</div>
<div class="f">oxxo<br>6</div>
</div>
</div>
<div class="camera">
<div class="space bv">
<div class="a">oxxo<br>1</div>
<div class="b">oxxo<br>2</div>
<div class="c">oxxo<br>3</div>
<div class="d">oxxo<br>4</div>
<div class="e">oxxo<br>5</div>
<div class="f">oxxo<br>6</div>
</div>
</div>
<!-- CSS 程式碼 -->
<style>
body {background: repeating-linear-gradient(white 0%,white 25px,black 25px,black 50px);}
div {
width: 100px;
height: 100px;
position: relative;
}
.camera {
perspective: 300px;
margin: 100px 50px;
float: left;
}
.space {
transform-style: preserve-3d;
transform: rotateY(45deg) rotateX(45deg);
}
.space div{
position: absolute;
width: 100px;
height: 100px;
border: 1px solid #000;
color: white;
font-size: 40px;
text-align: center;
line-height: 1;
opacity: .7;
}
.bv div {backface-visibility: hidden;}
.a {
transform: translateX(-50px) rotateY(-90deg);
background: #09f;
}
.b {
transform: translateX(50px) rotateY(90deg);
background: #f55;
}
.c {
transform: translateZ(-50px) rotateY(180deg);
background: #f90;
}
.d {
transform: translateZ(50px);
background: #0a0;
}
.e {
transform: translateY(50px) rotateX(-90deg);
background: #c0c;
}
.f {
transform: translateY(-50px) rotateX(90deg);
background: #666;
}
</style>
小结
这篇教学所介绍的 3D 转换样式,是操作 transform 的常见做法,通常第一次操作时会有点不容易理解,但只要熟悉摄影机、场景和元素的关联性,就可以很轻松的入门 transform 3D 转换与透视。
微信扫码关注
抖音扫码关注