CSS is a piece of technology I've always loved and strived to get better at. It is amazing what you can build with only CSS and how much cooler your applications will look like with just a bit of it. Today, we will be trying to build this little application which I hope you're anjoying (as you'll also enjoy building).
So without further ado, let's commence! First create an index.html file and paste the below code inside:
For the images, just download them from the github repo with the project (listed at the end of this article). Next, let's add a style.css file and reference it in the HTML file. Inside of it, let's add some minimal flexBox style CSS. This will only center our contents so far. If you want to dive deeper into FlexBox I highly recommend this article.
Now, let's start working on our cube a bit. We will style the .scene class (which will be the container for our cube) and the .cube class (which will contain the cube's faces). Paste further into style.css*:
/* scene & cube basic styles*/.scene{width: 150px;height: 150px;position: relative;display: flex; justify-content: center; align-items: center;}.cube{width:100%;height:100%;position: absolute;/* rotate horizontally 20deg and vertically 30 deg */transform:rotateX(-20deg)rotateY(30deg);/* slightly alter rotation for a better 3d effect */ transform-style: preserve-3d;}
*Note how we use position relative as container for position absolute (I'm sure you've heard this many times, or if you have not, then maybe you should study a bit CSS positions). Let's further add the generic styles for the cube faces:
*Note how now all faces look as if they are stacked together so we can see only the one on the top. The line setting position:absolute inside the rule for .cube-face does that (note that although we are inside a div that is positioned absolute, namely the .cube class the .cube-face still relates itself to the first relatively positioned ancestor namely the .scene class). Apart from positioning, the .cube-face class just centers the images. The .cube-face img selector just further positions the images better on the cube faces.
Next, let's build the actual cube. Pay attention to each individual face and how it's rotated on the axes in order to give the look and feel of an actual cube:
/* styles for inidvidual cube faces */.front{/* no rotation on vertical axis but push a bit in front on the Z axis*//* face is visible in default position of cube */transform:rotateY(0deg)translateZ(75px);}.back{/* rotate 180 deg on vertical axis and push in front on the Z axis *//* face not visible in default position of cube */transform:rotateY(180deg)translateZ(75px);}.right{/* rotate 90 degrees on vertical axis and pus in front on the Z axis *//* face not visible in default position of cube */transform:rotateY(90deg)translateZ(75px);}.left{/* rotate 90 degrees counter clockwise on vertical axis and pus in front on the Z axis *//* face is visible in default position of cube */transform:rotateY(-90deg)translateZ(75px);}.top{/* rotate horizontally to 90 deg and push in front on the Z axis*//* face is visible in default position of cube */transform:rotateX(90deg)translateZ(75px);}.bottom{/* rotate horizontally to 90 deg counter clockwis and push in front on the Z axis*//* face is not visible in default position of cube */transform:rotateX(-90deg)translateZ(75px);}
Next, let's add a keyframe for some dice rolling animation. A keyframe is a definition for an animation's steps (from what state to what state will the animation transition). Add the rule:
/* cube rotation keyframe */@keyframes randomRotate {0%{/* start from non rotated position */transform:rotateX(0deg)rotateY(0deg)rotateZ(0deg);}100%{/* rotate full 360 deg on all 3 axes */transform:rotateX(360deg)rotateY(360deg)rotateZ(360deg);}}
This on it's own will not do anything, so let's add 1 more class:
.animateCube{animation: randomRotate 1s linear;}
Next, apply the .animateCube class alongside the .cube class to the respective div, also temporarily add the infinite word after the 1s - this will make the animation run infinitely. After having tested it remove the infinite word (1 second is enough for us) and remove the .animatedCube class from the .cube div (we will be adding/removing it back with JavaScript).
Now, let's start working on the JavaScript part. Add a script referencing a script.js file and inside of it paste the below:
constfaceChangeHandler=()=>{console.log("This function triggers when cube animation finished");};functionhandleCube(){const cube =document.querySelector(".cube"); cube.classList+=" animateCube"; cube.addEventListener("animationend", faceChangeHandler);setTimeout(()=>{ cube.classList+=" reset";},2000);setTimeout(()=>{//remove event handler cube.removeEventListener("animationed", faceChangeHandler,true);//remove animation class cube.classList.remove("animateCube");},2100);}document.querySelector("#inputBtn").addEventListener("click", handleCube);
Now our cube rotates but it just comes back to its original position and it does not display anything. Let's quickly add 1 more class inside the CSS code:
Next, replace the contents in the JS code with the below:
//by default last index is one that is not in the array currentlylet lastFaceIndex =-1;//get random face to display a winnerfunctiongetRandomInt(max){returnMath.floor(Math.random()* max);}functionsetRandomFace(){const cube =document.querySelector(".cube");//positions to display the winner faceconst faces =[{x:0,y:0},// Front{x:0,y:180},// Back{x:0,y:90},// Right{x:0,y:-90},// Left{x:90,y:0},// Top{x:-90,y:0},// Bottom];let randomFaceIndex;//make sure index never repeatsdo{ randomFaceIndex =getRandomInt(faces.length);}while(randomFaceIndex === lastFaceIndex);//assign new latestFace lastFaceIndex = randomFaceIndex;//pick random face of diceconst randomFace = faces[randomFaceIndex];//edit cube's styles so that the winner face is displayedconst transformString =`rotateX(${randomFace.x}deg) rotateY(${randomFace.y}deg)`; cube.style.transform= transformString;}constfaceChangeHandler=()=>{console.log("This function triggers when cube animation finished");setRandomFace();};functionhandleCube(){const cube =document.querySelector(".cube"); cube.classList.remove("reset"); cube.classList+=" animateCube"; cube.addEventListener("animationend", faceChangeHandler);setTimeout(()=>{ cube.classList+=" reset";},2000);setTimeout(()=>{//remove event handler cube.removeEventListener("animationed", faceChangeHandler,true);//remove animation class cube.classList.remove("animateCube");},2100);}document.querySelector("#inputBtn").addEventListener("click", handleCube);
With this, our functionality is pretty much done. I just want to add 2 more improvements to this little project to make it even cooler.
First, I d like for us to display a winner (so the character on the winning face of the cube). Let's start by adding these styles in the CSS:
functiongetSingerName(idx){let name ="";switch(idx){case0: name ="Haku";break;case1: name ="Lily";break;case2: name ="Meiko";break;case3: name ="Luka";break;case4: name ="Teto";break;case5: name ="Miku";break;default: name ="Miku";break;}return name;}functiondisplayWinner(index){const images =document.querySelectorAll("img");const arrImg =Array.from(images);const target = arrImg[index];let srcToShow ="./";switch(index){case0: srcToShow ="./vocaloids/haku.gif";break;case1: srcToShow ="./vocaloids/lily.gif";break;case2: srcToShow ="./vocaloids/meiko.gif";break;case3: srcToShow ="./vocaloids/luka.gif";break;case4: srcToShow ="./vocaloids/teto.gif";break;case5: srcToShow ="./vocaloids/miku.gif";break;default: srcToShow ="./vocaloids/miku.gif";}document.querySelector(".winner").innerHTML=`<divclass="child"><h1>${getSingerName(index)} will sing for you:
<br><imgclass="thumbnail-small"src=${srcToShow}/></h1></div>`;}
Finally in the setRandomFace() call after setting the cube.style.transform property, add the below lines:
//displayWinnerdisplayWinner(randomFaceIndex);
Now before really wrapping it up, I just want to make the button way cooler. However the styles for it are not mine, they belong to JoshComeau who is an amazing developer and whose blog I've been reading for a while. He has this cool button tutorial and I took the liberty of reusing it for this article/project because it just looks way better than anything I ve seen before. So after having credited the author properly, let's just paste the button CSS code in the file directly: