css3 实现一个旋转的掘金方块

728 阅读5分钟

最近写了个如下所示的会自动旋转的掘金方块,本篇文章就来还原下实现的过程,对其间主要用到的知识点进行些梳理。

前期准备

先准备好一个 <div class="wrap"> 作为展示区域,<div class="cube"> 作为大盒子,里面包裹上 6 个 div 作为方块的 6 个面:

<div class="wrap">
  <div class="cube">
    <div class="face1"></div>
    <div class="face2"></div>
    <div class="face3"></div>
    <div class="face4"></div>
    <div class="face5"></div>
    <div class="face6"></div>
  </div>
</div>

样式如下,.cube 的重点在于设置 position: relative;,以便其子元素 .cube div 设置 position: absolute;,从而移出正常文档流,在单独的渲染层绘制,避免之后添加动画引发其它没有动画的节点产生联动,导致需要重新计算布局,发生回流(Reflow)重绘(Repaint),影响性能。

* {
  box-sizing: border-box;
}

.wrap {
  width: 100%;
  height: 200px;
  border: 1px solid red;
}
.cube {
  position: relative;
  width: 100px;
  height: 100px;
  left: 50%;
  top: 50%;
  margin-top: -50px;
  margin-left: -50px;
  border: 1px solid orange;
}

.cube div {
  position: absolute;
  width: 100%;
  height: 100%;
  background-color: #1e80ff;
  border: 1px solid #fff;
  opacity: 0.95;
}

image.png

坐标系与 transform

html 中每个元素都有自己的坐标系,默认情况下 x 轴方向朝右,y 轴方向朝下,z 轴方向朝向屏幕外,原点默认在左上角。当给某个元素设置了 transform 属性后,该元素的变形原点,即 transform-origin 的默认值为 50% 50%,故而坐标系的原点会移动到元素的中心点,坐标系发生改变,且改变会影响元素后续的变换:

2024-04-11_103808.png

transform 的值都是些函数,能让给定的元素发生旋转,缩放,倾斜或平移。它们的顺序也是需要注意的,顺序不同可能导致结果不同。 现在给 .cube 添加上 transform 属性如下:


.cube {
  // ...
  transform: rotateX(-26deg) rotateY(28deg);
}

.cube 以及其内部的 6 个 div 就都会绕着 x 轴逆时针旋转 26°,绕着 y 轴顺时针旋转 28°(注意单位是角度 deg),得到下图所示的形变:

transform-style 与 3D 空间

“稀”字面

.face1 添加如下样式,让其沿着 z 轴正方向平移 50 px:

.face1 {
  /* 或 transform: translate3d(0, 0, 50px); */
  transform: translateZ(50px);
}

想要得到预期的效果,关键是让 transform 支持 3D 的变换,这就需要给 .cube,也就是方块的 6 个面的父容器,设置 transform-style 属性为 preserve-3d,以表示其子元素是位于 3D 空间,就会显示出子元素之间的遮挡关系:

.cube {
  // ...
  transform-style: preserve-3d;
}

.face1 展现“稀”字,并添加合适样式:

<div class="face1"></div>
.cube div {
  // ...
  font-size: 30px;
  color: #fff;
  text-align: center;
  line-height: 100px;
}

现在得到的效果如下图所示,我们已经实现了正方体的“稀”字面了: image.png

“掘”字面

接着实现“掘”字面:

<div class="face2"></div>

它正好在“稀”字面的对面,所以我们只需要让它沿着 z 轴的负方向平移 50 px:

.face2 {
  transform: translateZ(-50px);
}

此时如果我们旋转 .cube 到背面观察得到的效果,会发现字写反了:

image.png

所以对于 .face2 还需要让其绕着 y 轴转 180°,注意两个变换函数的顺序:

.face2 {
  transform: translateZ(-50px) rotateY(180deg);
}

backface-visibility

这里补充说明下 backface-visibility 属性,其用于指定当元素背面朝向观察者时是否可见。如果给 .face2 添加上 backface-visibility: hidden;

.face2 {
  transform: translateZ(-50px);
  backface-visibility: hidden;
}

则当我们手动旋转 .cube 时,可以看到当处于背面的“掘”字面旋转到朝向我们的时候,它就会变得不可见:
GIF 2023-9-8 17-10-45.gif

backface-visibility 的值为默认的 visible 时,旋转到 .cube 的背面才能看到“掘”字面。

“金”字面

接着实现“金”字面:

<div class="face3">金</div>

无非是让它绕着 y 轴逆时针旋转 90°,然后再沿着旋转后得到的新坐标系的 z 轴正方向平移 50px(rotateY(-90deg) 也可以写成 rotate3d(0, 1, 0, -90deg)):

.face3 {
  transform: rotateY(-90deg) translateZ(50px);
}

“土”字面

“土”字面在“金”字面对面,只需要改为绕 y 轴顺时针旋转即可:

.face4 {
  transform: rotateY(90deg) translateZ(50px);
}

至此效果如下图: image.png

logo 面

剩下两个面为 logo 面,先设置背景图:

.face5,
.face6 {
  background-image: url(./imgs/juejin_white.png);
  background-size: cover;
  background-position: center;
}

然后让它俩分别绕 x 轴顺、逆时针旋转 90°,再都沿着 z 轴正方向平移 50px:

.face5 {
  transform: rotateX(90deg) translateZ(50px);
}

.face6 {
  transform: rotateX(-90deg) translateZ(50px);
}

至此,我们得到了一个静止的掘金方块了: image.png

添加帧动画

.cube,也就是上图中位于方块内部的红色平面添加旋转的帧动画:

.cube {
  // ...
  animation: spin 10s infinite cubic-bezier(0.6, -0.28, 0.74, 0.05);
}

@keyframes spin {
  0% {
    transform: rotateX(-26deg) rotateY(28deg);
  }
  70%,
  100% {
    transform: rotateX(-26deg) rotateY(-332deg);
  }
}

动画效果为 0s ~ 7s 旋转一周(开始旋转时有些许回转的效果),然后保持 3s 不动,再继续下一个变换周期。最后我们再隐藏掉 .cube 的背景颜色和边框线,就大功告成了。

关于性能优化

在设置动画时,一旦使用了 translateZ()translate3d()scale3d()rotate3d() 等这样涉及到 3D 变换的函数,浏览器就会创建一个合成层(Composited Layer)来启动 GPU 进行硬件加速,优化渲染性能。我们可以通过浏览器查看到新创建的这些合成层,并且可以看到它们的合层原因:

image.png

但是请注意合成层会消耗内存,因此也不能滥用。

感谢.gif 点赞.png