This website requires JavaScript to deliver the best possible experience.
Building a Magic Cube with CSS

Building a Magic Cube with CSS

What we're going to build?

In this article, we’ll show you easy it is to create a 3D cube using CSS, with an image attached to each face of it. We’ll also be able to rotate the cube and flatten it so every face shows its corresponding image.

We’re basically building an image gallery of a different kind where images are represented as cube faces in the 3D space.Here’s what you’ll be able to create after reading this post.

We called it the magic cube:

Building a Magic Cube with CSS

The cube can hold up to 6 different images on each of its sides, and double clicking any of them will flatten the cube out, yielding the following result of images spread out next to each other:

Building a Magic Cube with CSS

Let’s get started and take it one step at a time.

Thinking it first

Let’s have a quick reminder about axes of the three dimensional Cartesian coordinate system.

The Cartesian Coordinate System for a three-dimensional space is an ordered triplet of lines (axes) that are pair-wise perpendicular, have a single unit of length for all three axes and have an orientation of each axis. - Wikipedia
Building a Magic Cube with CSS

The x-axis represents the horizontal axis, the y-axis is the vertical axis, and the z-axis is supposed to be coming out from the screen towards you.

All of out work when it comes to rotations will be based upon these axes, so feel free to go more in depth if you’d like.

Basic cube HTML structure

Each face of the cube will have its own HTML:

<div>

Element, with a class name assigned to it that corresponds the name of the face, cube-top, cube-bottom, cube-left, ...

We'll also wrap the 6 faces together in another container:

<div>

Element with the class cube, since we need them to be rotated together as whole.

The cube container will also have a parent wrapper of its own of the class cube-wrapper.

<div class="cube-wrapper">
    <div class="cube">
        <div class="cube-top"></div>
        <div class="cube-bottom"></div>
        <div class="cube-left"></div>
        <div class="cube-right"></div>
        <div class="cube-front"></div>
        <div class="cube-back"></div>
    </div>
</div>

Attaching the images

Attaching an image to each of the cube side is pretty easy. We'll use the background-image CSS property to set the image of each face of the cube.

<div class="cube-wrapper"> 
	<div class="cube">
        <div class="cube-top" style="background-image: url('')"></div>
        <div class="cube-bottom" style="background-image: url('')"></div>
        <div class="cube-left" style="background-image: url('')"></div>
        <div class="cube-right" style="background-image: url('')"></div>
        <div class="cube-front" style="background-image: url('')"></div>
        <div class="cube-back" style="background-image: url('')"></div>
    </div>
</div>

Simply add the path of the image inside the url parentheses.

Setting the stage

First, we need to prepare the outermost

.cube-wrapper

Element, which wraps our cube and its faces, to act as the stage for our cube. We use the CSS perspective property to give it the needed depth.

You can read how the perspective works properly on MDN website.

.cube-wrapper {
    position: relative;
    height: 200px;
    width: 200px;
    margin: 100px;
    perspective: 1000px;
}

The 3D Cube

The cube will be 200 pixels wide, and we’ll give it a position of relative, so each face can be relative to it. We’ll also give it a preserve-3d transform style to keep the cube in the 3D form, otherwise its children will be rendered as flattened images.

.cube {
    position: relative;
    width: 200px;
    transform-style: preserve-3d;
    transform-origin: 100px 100px;
}

Positioning the face

In order to have the 3D look of a cube, we need to position each face accordingly in its appropriate position. Understanding both the Cartesian coordinate system and how it works in CSS will go a long way in understanding this step.

By adding different translations and rotations to each face, we can position them properly in the 3D space.

.cube-top {
  -webkit-transform: translate3d(0, -100px, 0) rotateX(90deg);
          transform: translate3d(0, -100px, 0) rotateX(90deg);
}
.cube-bottom {
  -webkit-transform: translate3d(0, 100px, 0) rotateX(90deg);
          transform: translate3d(0, 100px, 0) rotateX(90deg);
}
.cube-left {
  -webkit-transform: translate3d(-100px, 0, 0) rotateY(90deg);
          transform: translate3d(-100px, 0, 0) rotateY(90deg);
}
.cube-right {
  -webkit-transform: translate3d(100px, 0, 0) rotateY(-90deg);
          transform: translate3d(100px, 0, 0) rotateY(-90deg);
}
.cube-front {
  -webkit-transform: translate3d(0, 0, 100px) rotateY(0);
          transform: translate3d(0, 0, 100px) rotateY(0);
}
.cube-back {
  -webkit-transform: translate3d(0, 0, -100px) rotateY(-180deg);
          transform: translate3d(0, 0, -100px) rotateY(-180deg);
}

Note: The order of the two operations here matter, this is because of the nature of how matrices multiplication works.

Getting interactive

We’ll use JavaScript to add interactivity to the cube. First, we declare and define some global variables.

let el = null;
let wrapper = null;
let currentX = 0;
let currentY = 0;
let rorateX = 0;
let rotateY = 0;
let opened = false;

Then, we define a function called Cube to make our own DOM selector and also call our initialization functions.

function Cube(selector) {
    el = document.querySelector(element);
    _initData();
    _initEvents();
}

Here’s our function for initializing our data, and selecting the cube:

function _initData() {
    wrapper = el.parentNode;
    el.style.transform = 'translate3d(0, 0, 0) rotateX(-200deg) rotateY(45deg)'
}

In the above code snippet, we set the cube rotation angles for when the page loads. You can use any values.

Now we define our function to initialize the drag event listener for when we click and hold the left mouse button on one of the cube faces. The function will also assign the values of the clicked x and y coordinates to their respective variables.

And we also get the rotation values through a regular expression. Notice that we had to parse what the RegEx returns as a number, since it returns a string by default.

We also add an event listener to listen when a double click occurs on the cube.

function _initEvents() {
    el.addEventListener('mousedown', (event) => {
        event.preventDefault();
        currentX = event.clientX;
        currentY = event.clientY;
        rotateX = Number(el.style.transform.match(/rotateX\((-?[0-9]+(\.[0-9])?)*deg\)/)[1]);
        rotateY = Number(el.style.transform.match(/rotateY\((-?[0-9]+(\.[0-9])?)*deg\)/)[1]);
        
        document.addEventListener('mousemove', drag);
    	document.addEventListener('mouseup', release)
    });
    e.addEventListener('dblclick', open);
}

Here’s the drag function, which calculates the difference between the coordinates to rotate the cube:

function drag(event) {
    let draggedX = (event.clientX - currentX);
    let draggedY = (event.clientY - currentY);
    
    // check if the left mouse button is clicked
    if(event.buttons !== 1) return;
    el.style.transform = `rotateX(${rotateX - (draggedY / 2)}deg) rotateY(${rotateY + (draggedX / 2)}deg)`;
}

We also define a function that removes the listeners when we stop holding the mouse button:

function release(event) {
    document.removeEventListener('mousemove', drag);
    document.removeEventListener('mouseup', release);
}

Flattening the cube

We will define a class for the flattened cube state. Using appropriate transforms, we can present each side of the cube next to the other:

.cube.is-opened {
    animation: none;
    opacity: 1;
    transform: translate3d(0, 0, 0) rotateY(0deg) rotateX(0deg) !important;
}

.cube.is-opened .cube-top {
    transform: translate3d(-550px, 0, 0) rotateX(0);
}
.cube.is-opened .cube-bottom {
    transform: translate3d(-550px, 0, 0) rotateX(0);
}
.cube.is-opened .cube-left {
    transform: translate3d(-330px, 0, 0) rotateY(0);
}
.cube.is-opened .cube-right {
    transform: translate3d(330px, 0, 0) rotateY(0);
}
.cube.is-opened .cube-front {
    transform: translate3d(110px, 0, 0) rotateY(0);
}
.cube.is-opened .cube-back {
    transform: translate3d(-110px, 0, 0) rotateY(0);
}
And that’s it! We’re done creating a 3D cubic image gallery. Don’t forget to add your own images URLs in the html markup.

Comments

More Articles