Skip to content

CSS 3D 基础知识 ​

本文默认读者掌握一定的 CSS 3D 知识,能够绘制初步的 3D 动画效果。当然这里会再简单过一下 CSS 3D 的基础知识。

使用 transform-style 启用 3D 模式 ​

要利用 CSS3 实现 3D 的效果,最主要的就是借助 transform-style 属性。transform-style 只有两个值可以选择:

css
// 语法:
transform-style: flat|preserve-3d;

transform-style: flat; // 默认,子元素将不保留其 3D 位置
transform-style: preserve-3d; // 子元素将保留其 3D 位置。

当我们指定一个容器的 transform-style 的属性值为 preserve-3d 时,容器的后代元素便会具有 3D 效果,这样说有点抽象,也就是当前父容器设置了 preserve-3d 值后,它的子元素就可以相对于父元素所在的平面,进行 3D 变形操作。

利用 perspective & perspective-origin 设置 3D 视距,实现透视/景深效果 ​

perspective 为一个元素设置三维透视的距离,仅作用于元素的后代,而不是其元素本身。

简单来说,当元素没有设置 perspective 时,也就是当 perspective:none/0 时所有后代元素被压缩在同一个二维平面上,不存在景深的效果。

而如果设置 perspective 后,将会看到三维的效果。

css
// 语法
perspective: number|none;

// 语法
perspective-origin: x-axis y-axis;
// x-axis : 定义该视图在 x 轴上的位置。默认值:50%
// y-axis : 定义该视图在 y 轴上的位置。默认值:50%

而 perspective-origin 表示 3D 元素透视视角的基点位置,默认的透视视角中心在容器是 perspective 所在的元素,而不是他的后代元素的中点,也就是 perspective-origin: 50% 50%。

通过绘制 Webpack Logo 熟悉 CSS 3D ​

对于初次接触 CSS 3D 的同学而言,可以通过绘制正方体快速熟悉语法,了解规则。

而 Webpack 的 Logo,正是由 2 个 立方体组成:

img

以其中一个正方体而言,实现它其实非常容易:

  1. 一个正方体由 6 个面组成,所以首先设定一个父元素 div,然后这个 div 再包含 6 个子 div,同时,父元素设置 transform-style: preserve-3d;
  2. 6 个子元素,依次首先旋转不同角度,再通过 translateZ 位移正方体长度的一半距离即可
  3. 父元素可以通过 transform 和 perspective 调整视觉角度

以一个正方体为例子,简单的伪代码如下:

html
<ul class="cube-inner">
  <li class="top"></li>
  <li class="bottom"></li>
  <li class="front"></li>
  <li class="back"></li>
  <li class="right"></li>
  <li class="left"></li>
</ul>
scss
.cube {
  width: 100px;
  height: 100px;
  transform-style: preserve-3d;
  transform-origin: 50px 50px;
  transform: rotateX(-33.5deg) rotateY(45deg);

  li {
    position: absolute;
    top: 0;
    left: 0;
    width: 100px;
    height: 100px;
    background: rgba(141, 214, 249);
    border: 1px solid #fff;
  }
  .top {
    transform: rotateX(90deg) translateZ(50px);
  }
  .bottom {
    transform: rotateX(-90deg) translateZ(50px);
  }
  .front {
    transform: translateZ(50px);
  }
  .back {
    transform: rotateX(-180deg) translateZ(50px);
  }
  .left {
    transform: rotateY(-90deg) translateZ(50px);
  }
  .right {
    transform: rotateY(90deg) translateZ(50px);
  }
}

叠加两个,调整颜色和透明度,我们可以非常轻松的实现 Webpack 的 LOGO:

img

当然,这里的 LOGO 为了保证每条线条视觉上的一致性,其实是没有设置景深效果 perspective 的,我们可以尝试给顶层父容器添加一下如下代码,通过 transform 和 perspective 调整视觉角度,设置景深效果:

css
.father {
  transform-style: preserve-3d;
  perspective: 200px;
  transform: rotateX(10deg);
}

就可以得到真正的 3D 效果,感受很不一样:


实现文字的 3D 效果 ​

首先,看看一些有意思的 CSS 3D 文字特效。

要实现文字的 3D 效果,看起来是立体的,通常的方式就是叠加多层。

下面有一些实现一个文字的 3D 效果的方式。

假设我们有如下结构:

html
<div class="g-container">
  <p>Lorem ipsum</p>
</div>

如果什么都不加,文字的展示可能是这样的:

img

我们可以通过叠加阴影多层,营造 3D 的感觉,主要是合理控制阴影的距离及颜色,核心 CSS 代码如下:

css
p {
  text-shadow: 4px 4px 0 rgba(0, 0, 0, 0.8), 8px 8px 0 rgba(0, 0, 0, 0.6),
    12px 12px 0 rgba(0, 0, 0, 0.4), 16px 16px 0 rgba(0, 0, 0, 0.2),
    20px 20px 0 rgba(0, 0, 0, 0.05);
}

img

这样,就有了基础的 3D 视觉效果。

3D 氖灯文字效果 ​

基于此,我们可以实现一些 3D 文字效果,来看一个 3D 氖灯文字效果,核心就是:

  • 利用 text-shadow 叠加多层文字阴影
  • 利用 animation 动态改变阴影颜色
html
<div class="container">
  <p class="a">CSS 3D</p>
  <p class="b">NEON</p>
  <p class="a">EFFECT</p>
</div>

核心 CSS 代码:

css
.container {
  transform: rotateX(25deg) rotateY(-25deg);
}
.a {
  color: #88e;
  text-shadow: 0 0 0.3em rgba(200, 200, 255, 0.3), 0.04em 0.04em 0 #112,
    0.045em 0.045em 0 #88e, 0.09em 0.09em 0 #112, 0.095em 0.095em 0 #66c, 0.14em
      0.14em 0 #112, 0.145em 0.145em 0 #44a;
  animation: pulsea 300ms ease infinite alternate;
}
.b {
  color: #f99;
  text-shadow: 0 0 0.3em rgba(255, 100, 200, 0.3), 0.04em 0.04em 0 #112,
    0.045em 0.045em 0 #f99, 0.09em 0.09em 0 #112, 0.095em 0.095em 0 #b66, 0.14em
      0.14em 0 #112, 0.145em 0.145em 0 #a44;
  animation: pulseb 300ms ease infinite alternate;
}
@keyframes pulsea {
  // ... 阴影颜色变化
}
@keyframes pulseb {
  // ... 阴影颜色变化
}

可以得到如下效果:

利用 CSS 3D 配合 translateZ 实现真正的文字 3D 效果 ​

当然,上述第一种技巧其实没有运用 CSS 3D。下面我们使用 CSS 3D 配合 translateZ 再进一步。

假设有如下结构:

html
<div>
  <h1>Glowing 3D TEXT</h1>
  <h1>Glowing 3D TEXT</h1>
  <h1>Glowing 3D TEXT</h1>
  <h1>Glowing 3D TEXT</h1>
  <h1>Glowing 3D TEXT</h1>
  <h1>Glowing 3D TEXT</h1>
  <h1>Glowing 3D TEXT</h1>
  <h1>Glowing 3D TEXT</h1>
  <h1>Glowing 3D TEXT</h1>
  <h1>Glowing 3D TEXT</h1>
</div>

我们通过给父元素 div 设置 transform-style: preserve-3d,给每个 <h1> 设定不同的 translateZ() 来达到文字的 3D 效果:

css
div {
  transform-style: preserve-3d;
}
h1:nth-child(2) {
  transform: translateZ(5px);
}
h1:nth-child(3) {
  transform: translateZ(10px);
}
h1:nth-child(4) {
  transform: translateZ(15px);
}
h1:nth-child(5) {
  transform: translateZ(20px);
}
h1:nth-child(6) {
  transform: translateZ(25px);
}
h1:nth-child(7) {
  transform: translateZ(30px);
}
h1:nth-child(8) {
  transform: translateZ(35px);
}
h1:nth-child(9) {
  transform: translateZ(40px);
}
h1:nth-child(10) {
  transform: translateZ(45px);
}

当然,辅助一些旋转,色彩变化,就可以得到更纯粹一些 3D 文字效果:

利用距离、角度及光影构建不一样的 3D 效果 ​

还有一种很有意思的技巧,制作的过程需要比较多的调试。

合理的利用距离、角度及光影构建出不一样的 3D 效果。看看下面这个例子,只是简单是设置了三层字符,让它们在 Z 轴上相距一定的距离。

简单的伪代码如下:

html
<div>
  <span class="C">C</span>
  <span class="S">S</span>
  <span class="S">S</span>
  <span></span>
  <span class="3">3</span>
  <span class="D">D</span>
</div>
scss
$bright: #afa695;
$gold: #867862;
$dark: #746853;
$duration: 10s;
div {
  perspective: 2000px;
  transform-style: preserve-3d;
  animation: fade $duration infinite;
}
span {
  transform-style: preserve-3d;
  transform: rotateY(25deg);
  animation: rotate $duration infinite ease-in;

  &:after,
  &:before {
    content: attr(class);
    color: $gold;
    z-index: -1;
    animation: shadow $duration infinite;
  }
  &:after {
    transform: translateZ(-16px);
  }
  &:before {
    transform: translateZ(-8px);
  }
}
@keyframes fade {
  // 透明度变化
}
@keyframes rotate {
  // 字体旋转
}
@keyframes shadow {
  // 字体颜色变化
}

简单捋一下,上述代码的核心就是:

  1. 父元素、子元素设置 transform-style: preserve-3d
  2. 用 span 元素的两个伪元素复制两个相同的字,利用 translateZ() 让它们在 Z 轴间隔一定距离
  3. 添加简单的旋转、透明度、字体颜色变化

可以得到这样一种类似电影开片的标题 3D 动画,其实只有 3 层元素,但是由于角度恰当,视觉上的衔接比较完美,看上去就非常的 3D。

img

为什么上面说需要合理的利用距离、角度及光影呢?

还是同一个动画效果,如果动画的初始旋转角度设置的稍微大一点,整个效果就会穿帮:

img

可以看到,在前几帧,能看出来简单的分层结构。又或者,简单调整一下 perspective,设置父容器的 perspective 由 2000px 改为 500px,穿帮效果更为明显:

8

也就是说,在恰当的距离,合适的角度,我们仅仅通过很少的元素,就能在视觉上形成比较不错的 3D 效果。

3D 计数器 ​

当然,发挥想象,我们还可以利用 3D 文字效果,制作出非常多有意思的效果。

譬如这个,我之前运用在我们业务的可视化看板项目中的 3D 计数器:

img

代码比较长,就不贴出来了,但是也是使用纯 CSS 可以实现的效果。

完整的代码,你可以猛击这里 CSS 灵感 -- 3D 数字计数动画

空间效果 ​

嗯,上述章节主要是关于文字的 3D 效果,下面我们继续探寻 3D 在营造空间效果上的神奇之处。

优秀的 3D 效果,能让人有一种身临其境的感觉,都说 CSS 3D 其实作用有限,能做的不多,但是不代表它不能实现酷炫逼真的效果。

要营造逼真的 3D 效果,关键是恰当好处的运用 perspective 属性。

简单掌握原理,我们也可以很轻松的利用 CSS 3D 绘制一些非常有空间美感的效果。

这里我带领大家快速绘制一副具有空间美感的 CSS 3D 作品。

空间 3D 效果热身 ​

首先,我们借助 Grid/Flex 等布局,在屏幕上布满格子(item),随意点就好:

html
<ul class="g-container">
  <li></li>
  <li></li>
  // ... 很多子 li
  <li></li>
</ul>

初始背景色为黑色,每个 item 填充为白色

img

接着,改变下每个 item 的形状,让他变成长条形的,可以改变通过改变 item 宽度,使用渐变填充部分等等方式:

img

接下来,父容器设置 transform-style: preserve-3d 和 perspective,子元素设置 transform: rotateX(45deg),神奇的事情就发生了:

img

Wow,仅仅 3 步,我们就初步得到了一副具有空间美感的图形,让我们再回到每个子 item 的颜色设置,给它们随机填充不同的颜色,并且加上一个 transform: translate3d() 的动画,一个简单的 CSS 3D 作品就绘制完成了:

img

基于这个技巧的变形和延伸,我们就可以绘制非常多类似的效果。

在这里,我再次推荐 CSS-Doodle 这个工具,它可以帮助我们快速的创造复杂 CSS 效果。

CSS-doodle 是一个基于 Web-Component 的库。允许我们快速的创建基于 CSS Grid 布局的页面,以实现各种 CSS 效果(或许可以称之为 CSS 艺术)。

我们可以把上述的线条切换成圆弧:

img

完整的代码可以戳这里,利用 CSS-Doodle 也就几十行:CodePen Demo - CSS-Doodle Random Circle

又譬如袁川老师创作的 Seeding:

img

利用图片素材 ​

当然,基于上述技巧,有的时候会认为利用 CSS 绘制一些线条、圆弧、方块比较麻烦。可以进一步尝试利用现有的素材基于 CSS 3D 进行二次创作,这里有一个非常有意思的技巧。

假设我们有这样一张图形:

img

这张图先放着备用。在使用这张图之前,我们会先绘制这样一个图形:

html
<div class="g-container">
  <div class="g-group">
    <div class="item item-right"></div>
    <div class="item item-left"></div>
    <div class="item item-top"></div>
    <div class="item item-bottom"></div>
    <div class="item item-middle"></div>
  </div>
</div>
css
body {
  background: #000;
}
.g-container {
  position: relative;
}
.g-group {
  position: absolute;
  width: 100px;
  height: 100px;
  left: -50px;
  top: -50px;
  transform-style: preserve-3d;
}
.item {
  position: absolute;
  width: 100%;
  height: 100%;
  background: rgba(255, 255, 255, 0.5);
}
.item-right {
  background: red;
  transform: rotateY(90deg) translateZ(50px);
}
.item-left {
  background: green;
  transform: rotateY(-90deg) translateZ(50px);
}
.item-top {
  background: blue;
  transform: rotateX(90deg) translateZ(50px);
}
.item-bottom {
  background: deeppink;
  transform: rotateX(-90deg) translateZ(50px);
}
.item-middle {
  background: rgba(255, 255, 255, 0.5);
  transform: rotateX(180deg) translateZ(50px);
}

一共设置了 5 个子元素,不过仔细看 CSS 代码,其中 4 个子元素都设置了 rotateX/Y(90deg/-90deg),也就是绕 X 轴或者 Y 轴旋转了 90°,在视觉上是垂直屏幕的一张平面,所以直观视觉上我们是不到的,只能看到一个平面 .item-middle。

我将 5 个子 item 设置了不同的背景色,结果如下:

img

现在看来,好像平平无奇,确实也是。

不过,见证奇迹的时候来了,此时,我们给父元素 .g-container 设置一个极小的 perspective,譬如,设置一个 perspective: 4px,看看效果:

css
.g-container {
  position: relative;
+ perspective: 4px;
}
// ...其余样式保持不变

此时,画风骤变,整个效果就变成了这样:

img

由于 perspective 生效,原本的平面效果变成了 3D 的效果。接下来,我们使用上面准备好的星空图,替换一下上面的背景颜色,全部都换成同一张图,神奇的事情发生了:

img

由于设置的 perspective 非常之下,而每个 item 的 transform: translateZ(50px) 设置的又比较大,所以图片在视觉上被拉伸的非常厉害。但是整体是充满整个屏幕的。

接下来,我们只需要让视角动起来,给父元素增加一个动画,通过控制父元素的 translateZ() 进行变化即可:

css
.g-container{
  position: relative;
  perspective: 4px;
  perspective-origin: 50% 50%;
}

.g-group{
  position: absolute;
  // ... 一些定位高宽代码
  transform-style: preserve-3d;
  + animation: move 8s infinite linear;
}

@keyframes move {
  0%{
    transform: translateZ(-50px) rotate(0deg);
  }
  100%{
    transform: translateZ(50px) rotate(0deg);
  }
}

看看,神奇美妙的星空穿梭的效果就出来了,Amazing:

img

美中不足之处在于,动画没能无限衔接上,开头和结尾都有很大的问题。

当然,这难不倒我们,我们可以:

  1. 通过叠加两组同样的效果,一组比另一组通过负的 animation-delay 提前行进,使两组动画衔接起来(一组结束的时候另外一组还在行进中)
  2. 再通过透明度的变化,隐藏掉 item-middle 迎面飞来的突兀感
  3. 最后,可以通过父元素的滤镜 hue-rotate 控制图片的颜色变化

我们尝试修改 HTML 结构如下:

html
<div class="g-container">
  <div class="g-group">
    <div class="item item-right"></div>
    <div class="item item-left"></div>
    <div class="item item-top"></div>
    <div class="item item-bottom"></div>
    <div class="item item-middle"></div>
  </div>
  <!-- 增加一组动画 -->
  <div class="g-group">
    <div class="item item-right"></div>
    <div class="item item-left"></div>
    <div class="item item-top"></div>
    <div class="item item-bottom"></div>
    <div class="item item-middle"></div>
  </div>
</div>

修改后的核心 CSS 如下:

css
.g-container {
  perspective: 4px;
  position: relative;
  // hue-rotate 变化动画,可以让图片颜色一直变换
  animation: hueRotate 21s infinite linear;
}

.g-group {
  transform-style: preserve-3d;
  animation: move 12s infinite linear;
}
// 设置负的 animation-delay,让第二组动画提前进行
.g-group:nth-child(2) {
  animation: move 12s infinite linear;
  animation-delay: -6s;
}
.item {
  background: url(https://z3.ax1x.com/2021/08/20/fLwuMd.jpg);
  background-size: cover;
  opacity: 1;
  // 子元素的透明度变化,减少动画衔接时候的突兀感
  animation: fade 12s infinite linear;
  animation-delay: 0;
}
.g-group:nth-child(2) .item {
  animation-delay: -6s;
}
@keyframes move {
  0% {
    transform: translateZ(-500px) rotate(0deg);
  }
  100% {
    transform: translateZ(500px) rotate(0deg);
  }
}
@keyframes fade {
  0% {
    opacity: 0;
  }
  25%,
  60% {
    opacity: 1;
  }
  100% {
    opacity: 0;
  }
}
@keyframes hueRotate {
  0% {
    filter: hue-rotate(0);
  }
  100% {
    filter: hue-rotate(360deg);
  }
}

最终完整的效果如下,星空穿梭的效果,整个动画首尾相连,可以一直无限下去,几乎没有破绽,非常的赞:

img

3D 无限延伸视角动画 ​

OK,当掌握了上述技巧之后,我们可以很容易的对其继续变形发散,实现各种各样的无限延伸的 3D 视角动画。

这里还有一个非常有意思的运用了类似技巧的动画:

img

原理与上述的星空穿梭大致相同,4 面墙的背景图使用 CSS 渐变可以很轻松的绘制出来,接下来就只是需要考虑如何让动画能无限循环下去,控制好首尾的衔接。

该效果最早见于 jkantner 的 CodePen,在此基础上我对其进行了完善和丰富,

视差效果 ​

由于 CSS 3D 的特性,它天生就非常适合拿来制作一些视差效果。

本章节的内容之前在我的另外一篇文章,也有过一些讨论 -- CSS 实现视差效果

原理就是:

  1. 我们给容器设置上 transform-style: preserve-3d 和 perspective: [x]px,那么处于这个容器的子元素就将位于 3D 空间中,
  2. 再给子元素设置不同的 transform: translateZ(),这个时候,不同元素在 3D Z 轴方向距离屏幕(我们的眼睛)的距离也就不一样
  3. 滚动滚动条,由于子元素设置了不同的 transform: translateZ(),那么他们滚动的上下距离 translateY 相对屏幕(我们的眼睛),也是不一样的,这就达到了滚动视差的效果。

核心代码表示就是:

html
<div class="g-container">
  <div class="section-one">translateZ(-1)</div>
  <div class="section-two">translateZ(-2)</div>
  <div class="section-three">translateZ(-3)</div>
</div>
css
html {
  height: 100%;
  overflow: hidden;
}

body {
  perspective: 1px;
  transform-style: preserve-3d;
  height: 100%;
  overflow-y: scroll;
  overflow-x: hidden;
}

.g-container {
  height: 150%;

  .section-one {
    transform: translateZ(-1px);
  }
  .section-two {
    transform: translateZ(-2px);
  }
  .section-three {
    transform: translateZ(-3px);
  }
}

总结就是父元素设置 transform-style: preserve-3d 和 perspective: 1px,子元素设置不同的 transform: translateZ,滚动滚动条,效果如下:

很明显,当滚动滚动条时,不同子元素的位移程度从视觉上看是不一样的,也就达到了所谓的滚动视差效果。

滚动视差文字阴影/虚影效果/多图展示 ​

那么,运用 translate3d 的视差效果,又能有一些什么好玩的效果呢?下面这个滚动视差文字阴影/虚影效果很有意思:

另外一种就是我们可以把这个技巧运用到类似个人主页,图片展示等一些大屏场景下。

核心就是给每张图片设置不同的 translateZ,给父元素设置一个 persepective 即可,这样,在上下滚动的过程中,就能出现简单的视差效果:

同理,这个滚动视差不仅仅可以作用于上下的滚动,对于左右方向的滚动也是同样生效的:

其他实用场景介绍 ​

在这一章节,会介绍一些有趣的,可以落地的 3D 效果或者动画。

404 Rolling Box ​

其核心就在于在一个 CSS 3D 立方体的基础上:

  1. 添加立方体的滚动动画
  2. 控制下落的缓动函数,及落地的震荡动画(为了效果更为逼真,运用了设计动画中的预备动作、跟随和重叠动画等技巧)
  3. 控制立方体及地面数字画面的平移
  4. 控制动画的无限循环

整体制作还是非常有难度的,但是用在自己的 404 页面,确实也是非常的酷炫。这个效果,我最早见于 Yusuke Nakaya 大神,完整的代码你可以戳这里

立方体进度条 ​

嗯,下面这个还是借助了立方体。我们来实现一个立方体进度条~

首先,实现一个立方体,结构如下:

html
<div class="demo-cube perspective">
  <ul class="cube">
    <li class="top"></li>
    <li class="bottom"></li>
    <li class="front"></li>
    <li class="back"></li>
    <li class="right"></li>
    <li class="left"></li>
  </ul>
</div>

img

我们可以把这个立方体想象成一个立体的进度条容器,通过控制 6 面的颜色,我们可以巧妙的得到一种 3D 进度条效果。

当然,其实我们不需要那么多面,4 个面即可,去掉左右,然后利用渐变修改一下立方体各个面的颜色,去掉 border,核心的 CSS 代码如下:

scss
.demo-cube {
  position: relative;

  .cube {
    position: absolute;
    top: 50%;
    left: 50%;
    width: 300px;
    height: 100px;
    transform-style: preserve-3d;
    transform: translate(-50%, -50%) rotateX(-33.5deg);

    li {
      position: absolute;
      width: 300px;
      height: 100px;
      background: linear-gradient(
        90deg,
        rgba(156, 39, 176, 0.3),
        rgba(255, 34, 109, 0.8) 70%,
        rgba(255, 255, 255, 0.6) 70%,
        rgba(255, 255, 255, 0.6)
      );
    }
    .top {
      transform: rotateX(90deg) translateZ(50px);
    }
    .bottom {
      transform: rotateX(-90deg) translateZ(50px);
    }
    .front {
      transform: translateZ(50px);
    }
    .back {
      transform: rotateX(-180deg) translateZ(50px);
    }
  }
}

我们就可以得到一个非常酷炫的 3D 进度条效果:

image

利用 CSS Property 给 3D 进度条加上动画 ​

当然,进度条嘛,它需要一个填充动画。由于我们使用的是渐变实现的进度条的进度,需要去控制其中的颜色百分比变化。

而正常而言,CSS 是不支持渐变的动画的,不过这也难不倒我们,因为我们可以使用 CSS @Property 。

简单改造一下代码:

css
@property --per {
  syntax: '<percentage>';
  inherits: false;
  initial-value: 0%;
}

.demo-cube .cube {
  .top,
  .front,
  .bottom,
  .back {
    background: linear-gradient(
      90deg,
      rgba(255, 217, 34, 0.6),
      rgba(255, 34, 109, 0.8) var(--per),
      rgba(255, 34, 109, 0.1) var(--per),
      rgba(255, 34, 109, 0.1)
    );
    animation: perChange 6s infinite;
  }
}

@keyframes perChange {
  0% {
    --per: 0%;
  }
  90%,
  to {
    --per: 80%;
  }
}

这样,我们就实现了一个会动的 3D 进度条,只需要控制 --per CSS 自定义属性即可,效果如下:

对于 CSS @Property 不算很了解的,可以看看作者的这篇文章 -- CSS @property,让不可能变可能,它的出现,让 CSS 极大的提升了制作各种动画的能力。