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)没有把距离拉开。。
其实这是因为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%的时间停留。基本达到了原效果。
精加工
上面的旋转效果我们通过keyframes来写,灵活性很差,不容易调整每次旋转90deg后的停留时间,逻辑较复杂的动画应该交由JS控制。
我发现原图在每次旋转之后,立方体的外围边框线总是很粗,这在视觉上能体现出了很强的空间感,而我的立方体所有的边框线都一致,空间感表现很差显得没那么真实。
我们发现正方体六个面中,只有位于一号面、三号面、四号面这三个面拥有较粗的边框线,而二号面在下面不参与线的变化,五六面在旋转中也参与边框线的变化。我们先不管三号面的变化,只看一四五六面的变化。
位置 | 1 | 4 | 5 | 6 |
---|---|---|---|---|
第一次旋转 | 4 | 6 | 1 | 5 |
第二次旋转 | 6 | 5 | 4 | 1 |
第三次旋转 | 5 | 1 | 6 | 4 |
第四次旋转 | 1 | 4 | 5 | 6 |
假设位于一四五六面所在的位置的面的边框线一直不变--都是粗的,而当立方体旋转时,面原来的位置发生改变。当第一次旋转时,一号面的位置上变为四号面;四号面的位置上变为六号面;五号面的位置上变为一号面;六号面的位置上变为五号面。以此类推,当第四次旋转时,所有面便回到了原来的位置上。
而我们设置位于一号位置的面边框线为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);
总结
制作一个立方体很简单,利用transform属性实现面的搭建就可以,但前提是要在父元素上设置transform-style为 preserve-3d模式。transform属性按值得顺序执行,当有一堆translate时,每一个值的顺序应当考虑清楚。
制作一个立方体很简单,但如何使其更加逼真,更加贴切地符合站点的内容才是真正需要考虑的。如果对这个立方体我没有进行精加工,那么效果将相差十万八千里,放上去也只会显得蹩脚。。。最后ES6的模板字符串真的很好用啊,哈哈哈哈哈?
通过原生JS,点击事件,鼠标按下、鼠标抬起和鼠标移动事件,实现3d立方体的拖动旋转,并将旋转角度实时的反应至界面上显示。<input type="text" class="xNum" value="&