玩转CSS 3D -正八面体与正十二面体
正八面体与正十二面体,这两个正多面体虽然组合的面比较多,不过因为具备了对称性,所以只需要制作出一半的结构,另外一半再用反转的方式接在一起即可。
正八面体
正八面体可以想像成“两个金字塔”叠合在一起,为了方便我们后续作整个金字塔旋转的动作,我们要用另外一个容器来把金字塔包装起来,可以想像成把金字塔的四个面变成一个群组,就可以针对这个群组做变形或移动,HTML的结构如下,space里面有box1和box2,box1是上半部的金字塔,box2是下半部的金字塔:
<div class="camera"> <div class="space"> <div class="box1"> <div class="face1"></div> <div class="face2"></div> <div class="face3"></div> <div class="face4"></div> </div> <div class="box2"> <div class="face1"></div> <div class="face2"></div> <div class="face3"></div> <div class="face4"></div> </div> </div> </div>
再来同样先针对camera和space做设定。
.camera{ width:200px; height:200px; -webkit-perspective-origin:50% 50%; -webkit-perspective:500px; } .space{ position:relative; width:100%; height:100%; border:1px dashed #000; -webkit-transform-style:preserve-3d; }
然后因为我们用到的面face都是三角形,同样要用border来达成,记得position要设定为absolute,并且由于box1,box2本身内部也是立体空间,所以同样要加上transform-style: preserve-3d的属性。
.space div{ position:absolute; } .box1, .box2{ transform-style:preserve-3d; } .box1 div,.box2 div{ width:0; height:0; border-width:0 50px 87px; border-style:solid; opacity:.4; }
再来制作box1的金字塔造型,由于正八面体每个面夹角为109.28度,又因为分为上两半,从中间差开,一半为54.64度,因此我们要旋转的角度是:90 - 54.64 = 35.36,所以待会我们要旋转的角度就以此为主,先看到face1,顺着x轴往内转35.36度。
.face1{ border-color:transparent transparent #f00; transform-origin:center bottom; transform:translateX(50px) translateY(50px) rotateX(35.36deg); }
face2则要先以右边为圆心,绕Y轴旋转90度,之后再绕X轴旋转35.36度。
.face2{ border-color:transparent transparent #00f; transform-origin:right bottom; transform:translateX(50px) translateY(50px) rotateY(-90deg) rotateX(-35.36deg); }
face3在face1的对面,所以先在Z轴移动100px,接着再绕X轴旋转35.36度。
.face3{ border-color:transparent transparent #0f0; transform-origin:left bottom; transform:translateX(50px) translateY(50px) translateZ(-100px) rotateX(-35.36deg); }
face4跟face2雷同,只是face4用左侧为圆心。
.face4{ border-color:transparent transparent #f90; transform-origin:left bottom; transform:translateX(50px) translateY(50px) rotateY(90deg) rotateX(-35.36deg); }
基本上到这边,已经完成了上半部box1的金字塔造型,这时后的box1与box2是重叠的,我们只要将box2反转并改变位置,就可以变成一个正八面体的造型,但这里会遇到一个很有意思的问题,box2变形的中心点在哪里?这时候就必须用到好几次的三角函数计算,因为是一个金子塔造型旋转的中心点,所以就要把Z轴考察进去,首先看到X轴设定为center是没问题的,Z轴因为金子塔的底部是100 x 100的正方形,所以中心点在50px的位置也很正常,但Y轴就比较麻烦,首先我们看到正三角形的一个边长为100px,中线的长度就是sin(60)x 100 = 86.6左右,再来用sin(54.64)x 86.6就得到金子塔的高度70.6,四舍五入一下就得到了71,因此当我们将变形中心设定在这边,旋转的时候就会绕着金子塔顶旋转,旋转180度之后,就要利用translateY来归位,要移动的距离约为100 + 71/2 = 135左右,但因为从一开始我们就都是用四舍五入的方式进行,难免最后用整数表现会有误差(两个金字塔接不起来),这时就必须要用手动微调,这里设定为-132px即可。
.box2{ transform-origin: center 71px -50px; transform: rotateX(180deg) translateY(-132px); }
同样的,旋转space让整个正八面体旋转,看起来更有立体感。
.space{ position:relative; width:100%; height:100%; -webkit-transform-style:preserve-3d; -webkit-transform-origin:center center -50px; -webkit-animation:s 4s linear infinite; } @-webkit-keyframes s{ 0%{ -webkit-transform:rotateY(0); } 100%{ -webkit-transform:rotateY(-359.9deg); } }
正十二面体
会作正八面体之后,正十二面体差不多也是同样的原理,只是正十二面体更加复杂,因为它是由十二个正五边形组成,不过这里要从正五边形的五个边去长出五个面,每个面的夹角为116.56度,180 - 116.56 = 63.44,待会用到的角度大概不会脱离这两个数值,当然因为有很多小数点,所以届时还是必须手动微调(因为像素最小单位是1)。
HTML的结构如下,也是分成两块,内容各有六个面。
<div class="camera"> <div class="space"> <div class="box1"> <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 class="box2"> <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> </div>
先设定一开始的CSS。
.camera{ width:200px; height:200px; perspective-origin:50% 0%; -moz-perspective-origin:50% 0%; -webkit-perspective-origin:50% 0%; perspective:500px; -moz-perspective:500px; -webkit-perspective:500px; } .space{ position:relative; width:100%; height:100%; border:1px dashed #000; transform-style:preserve-3d; -moz-transform-style:preserve-3d; -webkit-transform-style:preserve-3d; } .space div{ position:absolute; } .box1, .box2{ transform-style:preserve-3d; -moz-transform-style:preserve-3d; -webkit-transform-style:preserve-3d; }
再来是慢慢的画每个面,首先是画出所有的正五边形。
.box1 div,.box2 div{ width:162px; height:154px; opacity:.9; } .box1 div:before,.box2 div:before{ position:absolute; content:""; top:0; width:0; height:0; border-width:0 81px 59px; border-style:solid; border-color:transparent transparent #069; } .box1 div:after,.box2 div:after{ position:absolute; content:""; top:59px; left:0; width:100px; height:0; background:none; border-width:95px 31px 0; border-style:solid; border-color:#069 transparent transparent; }
因为是要由正五边形的五个边往外长五个面,所以face1就不做更动,届时再让box做旋转即可,而face2到face6这五个面,比较简单的做法就是直接先做Z轴旋转的动作,然后再进行Y轴旋转,再把各个面translate到对应的位置,不过这里要非常注意,因为我们先进行了Z轴与Y轴的旋转,所以各个面的坐标系统已经改变,可以参照下图,就可以明白该如何translate,基本上就是在Y轴旋转了-116.56度之后,各个面先朝向自己的Y轴移动69px,然后为了让各个边贴齐,必须要再往Y轴移动31px(cos(180-116.56)x 69),往Z轴移动62px(sin(180-116.56)x 69)。
因此face2到face6就可以几乎用完全一样的方式写出来(Z轴旋转角度每个面差72度)
.face2{ transform-origin:81px 85px 0; transform:rotateZ(0) rotateX(-116.56deg) translateY(-69px) translateY(-31px) translateZ(62px); } .face3{ transform-origin:81px 85px 0; transform:rotateZ(72deg) rotateX(-116.56deg) translateY(-69px) translateY(-31px) translateZ(62px); } .face3{ transform-origin:81px 85px 0; transform:rotateZ(72deg) rotateX(-116.56deg) translateY(-69px) translateY(-31px) translateZ(62px); } .face4{ transform-origin:81px 85px 0; transform:rotateZ(144deg) rotateX(-116.56deg) translateY(-69px) translateY(-31px) translateZ(62px); } .face5{ transform-origin:81px 85px 0; transform:rotateZ(-144deg) rotateX(-116.56deg) translateY(-69px) translateY(-31px) translateZ(62px); } .face6{ transform-origin:81px 85px 0; transform:rotateZ(-72deg) rotateX(-116.56deg) translateY(-69px) translateY(-31px) translateZ(62px); }
为了让每个面不同颜色,这里必须要改变伪元素的border色彩。
.box1 .face2:before{ border-color:transparent transparent #f00; } .box1 .face2:after{ border-color:#f00 transparent transparent; } .box1 .face3:before{ border-color:transparent transparent #0f0; } .box1 .face3:after{ border-color:#0f0 transparent transparent; } .box1 .face4:before{ border-color:transparent transparent #f90; } .box1 .face4:after{ border-color:#f90 transparent transparent; } .box1 .face5:before{ border-color:transparent transparent #09f; } .box1 .face5:after{ border-color:#09f transparent transparent; } .box1 .face6:before{ border-color:transparent transparent #f0f; } .box1 .face6:after{ border-color:#f0f transparent transparent; }
最后就针对box1与box2做旋转的动作,这里比较需要注意的是translateZ,整个正五边形的高度为276(sin(63.44)x 154 x 2),但位移的时候并非完全是这个高度,必须扣掉接合处短边的高度,因此会变成223(276 - sin(36)x 100)
.box1{ transform-origin:81px 85px 0; transform:rotateX(90deg) translateZ(-223px); } .box2{ transform-origin:81px 85px 0; transform:rotateX(-90deg); }
space加上动画效果,验证一下每个面是否都有接合的完美。
.space{ position:relative; width:100%; height:100%; -webkit-transform-style:preserve-3d; -webkit-transform-origin:81px 170px 0; -webkit-animation:s 4s linear infinite; } @-webkit-keyframes s{ 0%{ -webkit-transform:rotateY(0) rotateX(0); } 100%{ -webkit-transform:rotateY(-359.9deg) rotateX(359.9deg); } }