最近写了个如下所示的会自动旋转的掘金方块,本篇文章就来还原下实现的过程,对其间主要用到的知识点进行些梳理。
前期准备
先准备好一个 <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;
}
坐标系与 transform
html 中每个元素都有自己的坐标系,默认情况下 x 轴方向朝右,y 轴方向朝下,z 轴方向朝向屏幕外,原点默认在左上角。当给某个元素设置了 transform 属性后,该元素的变形原点,即 transform-origin 的默认值为 50% 50%,故而坐标系的原点会移动到元素的中心点,坐标系发生改变,且改变会影响元素后续的变换:
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;
}
现在得到的效果如下图所示,我们已经实现了正方体的“稀”字面了:
“掘”字面
接着实现“掘”字面:
<div class="face2">掘</div>
它正好在“稀”字面的对面,所以我们只需要让它沿着 z 轴的负方向平移 50 px:
.face2 {
transform: translateZ(-50px);
}
此时如果我们旋转 .cube 到背面观察得到的效果,会发现字写反了:
所以对于 .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 时,可以看到当处于背面的“掘”字面旋转到朝向我们的时候,它就会变得不可见:
当 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);
}
至此效果如下图:
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);
}
至此,我们得到了一个静止的掘金方块了:
添加帧动画
给 .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 进行硬件加速,优化渲染性能。我们可以通过浏览器查看到新创建的这些合成层,并且可以看到它们的合层原因:
但是请注意合成层会消耗内存,因此也不能滥用。