3D转换立方体搭建

DaDomain 2019-06-21

查看 webpack 官网文档,一直觉得官网中间的这个旋转的立方体非常契合webpack这款打包工具的理念。由一个大的立方体包围一个小立方体,转向却相反。经过这样的转换后一推凌乱的文件就被打包成整齐的资源。

动画非常棒,简洁形象。这也是给我留下深刻印象的原因。
再观细节,立方体的外围宽度明显宽于其他边框,这增加了立方体的景深,配合内部的小立方体更显逼真。

这么棒的效果,理应探究一下。哈哈哈哈?
成品图在这里

应用css的3D变换可以轻易地在三维空间中制造出一个立方体,再应用rotateY()属性,便呈现出一个旋转的立方体。这是思路

<span class="cube">
  <figure class="cube_outer">
    <section class="cube_face_outer">1</section>
    <section class="cube_face_outer">2</section>
    <section class="cube_face_outer">3</section>
    <section class="cube_face_outer">4</section>
    <section class="cube_face_outer">5</section>
    <section class="cube_face_outer">6</section>
  </figure>
  <figure class="cube_inner">
    <section class="cube_face_inner">1</section>
    <section class="cube_face_inner">2</section>
    <section class="cube_face_inner">3</section>
    <section class="cube_face_inner">4</section>
    <section class="cube_face_inner">5</section>
    <section class="cube_face_inner">6</section>
  </figure>
</span>

一共两个立方体,都被包围在span中。span元素当作立方体的container,用来固定里面立方体的位置,并为我们设置俯视的角度。

.cube {
  position: relative;
  display: block;
  transform-style: preserve-3d;
  transform: rotateX(-33.5deg) rotateY(45deg);
  width: 120px;
  margin: 100px auto;
}

立方体被figure元素包围,因为立方体是一个独立的自包含元素,比起其它如div或span的标签用figure标签十分契合。里面的六个面分为六个部分,用section标签也非常符合html5语义化的标准。
figure是立方体本身,它确定立方体的大小及位置,并为六个面提供3d环境设置。

.cube_outer {
  height: 120px;
  width: 120px;
  transform-style: preserve-3d;
  transition: transform 1s ease-out;
  display: inline-block;
}

接下来是六个面的设置,把共用的样式提取出来。

.cube_face_outer {
  background-color: rgba(141, 214, 249, 0.5);
  transition: border-width 0.2s ;
  position: absolute;
  width: 100%;
  height: 100%;
  border: 1px solid #fff;
  text-align: center;
  font-size: 36px;
}

最后确定六个面的位置了。六个面是相对于最外层的span来定位的。
如何把空间拉出来(提供面与面之间的距离)呢? translateZ()可以使物体在垂直于平面的方向移动,进而拉出空间感,再通过rotateX()或rotateY()来把立方体组装起来。

/*1-6 前 下 上 左 右 后*/
.cube_face_outer:nth-child(1) {
  transform: translateZ(60px) rotateX(0);
}
.cube_face_outer:nth-child(2) {
  transform: translateZ(60px) rotateX(-90deg);
}
.cube_face_outer:nth-child(3) {
  transform: translateZ(60px) rotateX(90deg);
}
.cube_face_outer:nth-child(4) {
  transform:translateZ(60px) rotateY(-90deg);
}
.cube_face_outer:nth-child(5) {
  transform:translateZ(60px) rotateY(90deg);
}
.cube_face_outer:nth-child(6) {
  transform:translateZ(60px) rotateY(180deg);
}

ok,依据上一步骤,我们设置了translateZ(60px)来拉开面之间的空间,然后再设置 rotateX()、rotateY()来组装立方体。但结果却出乎意料,面与面之间并未拉开距离!!六个面经过旋转交叉在了一起,translateZ(60px)没有把距离拉开。。

3D转换立方体搭建

其实这是因为transform属性按值的顺序来执行转换而导致的。对于每个面我们都先执行了translateZ(60px),而每个面都是相对与同一个元素span来定位,就是导致先执行完translateZ(60px)后,每个面其实是叠加在一起的,它们只是在垂直于平面的方向共同位移了60px而已,然后再执行rotateX、rotateY()便会出现六个面交叉的情况。解决方案很简单,因为transform按照值得顺序来进行转换,我们只要先进行rotateX、rotateY()让面在中心处摆出立方体的形状,然后利用translateZ(60px)位移拉开距离就可以了。

下一步就是实现旋转了。我们简单地设置一个帧动画。

@keyframes rot {
  0% {
    transform: rotateY(0deg)
  }
  10% {
    transform: rotateY(90deg)
  }
  25% {
    transform: rotateY(90deg)
  }
  35% {
    transform: rotateY(180deg)
  }
  50% {
    transform: rotateY(180deg)
  }
  60% {
    transform: rotateY(270deg)
  }
  75% {
    transform: rotateY(270deg)
  }
  85% {
    transform: rotateY(360deg)
  }
  100% {
    transform: rotateY(360deg)
  }
}
/*为立方体加上帧动画*/
.cube_outer {
  animation:rot 8s ease-out 0s infinite  forwards;
}

为了让立方体在每转过90deg后有停歇的效果,我设置每个90deg总占比例为15%,这样当立方体每旋转90deg后就有15%的时间停留。基本达到了原效果。


精加工

  1. 上面的旋转效果我们通过keyframes来写,灵活性很差,不容易调整每次旋转90deg后的停留时间,逻辑较复杂的动画应该交由JS控制。

  2. 我发现原图在每次旋转之后,立方体的外围边框线总是很粗,这在视觉上能体现出了很强的空间感,而我的立方体所有的边框线都一致,空间感表现很差显得没那么真实。

我们发现正方体六个面中,只有位于一号面、三号面、四号面这三个面拥有较粗的边框线,而二号面在下面不参与线的变化,五六面在旋转中也参与边框线的变化。我们先不管三号面的变化,只看一四五六面的变化。

位置1456
第一次旋转4615
第二次旋转6541
第三次旋转5164
第四次旋转1456

假设位于一四五六面所在的位置的面的边框线一直不变--都是粗的,而当立方体旋转时,面原来的位置发生改变。当第一次旋转时,一号面的位置上变为四号面;四号面的位置上变为六号面;五号面的位置上变为一号面;六号面的位置上变为五号面。以此类推,当第四次旋转时,所有面便回到了原来的位置上。

而我们设置位于一号位置的面边框线为f1,四号位置边框线为f4,其余为f。

const f1 = '1px 6px 6px 1px';
const f4 = '1px 1px 6px 6px';
const f = '1px';

取到各元素后,写一个roate函数。在rotate函数中我们不仅要控制立方体的边框线粗细,还要控制立方体的旋转。

const cube_outer = document.querySelector('.cube_outer');
const deg = 90;
let i = 0; //设置全局变量 i 来控制旋转,没旋转一次 i 加 1,进而旋转角度也增加 ${deg*i}deg

function rotate() {
  i += 1;
  cube_outer.style.transform = `translateX(-50%) scale3d(1, 1, 1) rotateX(0deg) rotateY(${deg*i}deg) rotateZ(0deg)`;

  if (i%4 === 1) { //转第一次
    face[0].style.borderWidth = `${f}`;
    face[4].style.borderWidth = `${f}`;
    face[3].style.borderWidth = `${f1}`;
    face[5].style.borderWidth = `${f4}`;


  }else if (i%4 === 2) { //第二次
    face[0].style.borderWidth = `${f}`;
    face[3].style.borderWidth = `${f}`;
    face[4].style.borderWidth = `${f4}`;
    face[5].style.borderWidth = `${f1}`;


  }else if (i%4 === 3) { //第三次
    face[5].style.borderWidth = `${f}`;
    face[3].style.borderWidth = `${f}`;
    face[0].style.borderWidth = `${f4}`;
    face[4].style.borderWidth = `${f1}`;


  } else if (i%4 === 0) {  //第四次归位
    face[4].style.borderWidth = `${f}`;
    face[5].style.borderWidth = `${f}`;
    face[0].style.borderWidth = `${f1}`;
    face[3].style.borderWidth = `${f4}`;

  }
}

基本逻辑就是这样,利用ES6的模板字符串给属性赋值方便了很多,在控制台中输入 rotate(),观察立方体的外围宽度发现效果实现了。最后我们需要把最上面的面--三号面集成进去。三号位置的面在第一次旋转后borderWidth设为f31,第二次旋转设为f32,第三次旋转设为f33,第四次旋转设为f34。

const f31 = '1px 6px 6px 1px';
const f32 = '1px 1px 6px 6px';
const f33 = '6px 1px 1px 6px';
const f34 = '6px 6px 1px 1px';


if (i%4 === 1) { //转第一次

  face[2].style.borderWidth = `${f31}`;

}else if (i%4 === 2) { //第二次

  face[2].style.borderWidth = `${f32}`;

}else if (i%4 === 3) { //第三次

  face[2].style.borderWidth = `${f33}`;

} else if (i%4 === 0) { //第四次归位

  face[2].style.borderWidth = `${f34}`;

}

和上一步代码放到一起,效果就实现了。然后再增加一个定时器。运行,哈哈哈,效果差不多了。当然里面还有一个小正方体,小正方体和大正方体是同样的,只是利用scale3d(0.5, 0.5, 0.5) 缩小至一半后放入,每次旋转的角度为${- deg*i}deg就ok了。

setInterval(function(){
  rotate();
},4000);

总结

  1. 制作一个立方体很简单,利用transform属性实现面的搭建就可以,但前提是要在父元素上设置transform-style为 preserve-3d模式。transform属性按值得顺序执行,当有一堆translate时,每一个值的顺序应当考虑清楚。

  2. 制作一个立方体很简单,但如何使其更加逼真,更加贴切地符合站点的内容才是真正需要考虑的。如果对这个立方体我没有进行精加工,那么效果将相差十万八千里,放上去也只会显得蹩脚。。。最后ES6的模板字符串真的很好用啊,哈哈哈哈哈?

相关推荐