Grow your CSS skills. Land your dream job.

Creating a 3D Cube Image Gallery

Published by Guest Author

The following is a guest post by Kushagra Gour (@chinchang457). Kushagra wrote to me to show me a fun interactive demo he made. It touches on many of the concepts of 3D transforms in CSS, a topic we haven't covered a ton here. So here's Kushagra taking the reins to explain these concepts through a demo.

I recently redesigned my website and came up with a 2-face 3D cube idea for the homepage and header. On hovering it rotates between my display pic and Twitter link. While doing so I thought why not extend the idea into a full 6-face cube which could be used as an image gallery. Here is what I came up with!

See the Pen 3D cube image gallery by Kushagra Gour (@chinchang) on CodePen

This tutorial outlines how you can make something like this, emphasizing the CSS3 3D concepts.

Breaking the cube

It is quite evident by seeing the cube that the 6 faces of the cube would be 6 different HTML elements. Six <div> elements in this case. Because they need to be rotated as one cube, they need to be in a container element. If we code this basic structure, we would have something like this:

<div class="cube">
    <div class="cube-face"></div>
    <div class="cube-face"></div>
    <div class="cube-face"></div>
    <div class="cube-face"></div>
    <div class="cube-face"></div>
    <div class="cube-face"></div>
 </div>

Also since we will need to reference each sides to style them, we should add appropriate classes to them.

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

Styling the faces

We don't see anything yet. So lets give some dimension and style to the faces.

$size: 150px; // cube length
.cube {
  width: $size;
  height: $size;
  position: relative;
}
.cube-face {
  width: inherit;
  height: inherit;
  position: absolute;
  background: red;
  opacity: 0.5;
}

See the Pen 3d cube gallery tutorial - P1 by Kushagra Gour (@chinchang) on CodePen

Note that every cube face has position set to absolute so they stack upon each other at one place. Now we can select each and position accordingly.

Also I have given opacity to each face so we can see through them and see what's happening.

CSS3 3D concepts

Lets get to know some concepts of CSS3 3D. To bring the front face a little closer to the eyes, we translate it on the Z-axis:

.cube-face-front {
  color: blue;
  transform: translate3d(0, 0, 20px);
}

You won't see any difference yet. Lets understand why.

perspective

As mentioned on MDN:

The perspective CSS property determines the distance between the z=0 plane and the user in order to give to the 3D-positioned element some perspective.

In simple terms, its value determines the amount of 3D-ness in the space. The lesser the value of this property, more profound the 3D effect is. Without this property, the elements are rendered on the screen using Parallel projection in which the projection lines are parallel to each other. Therefore no matter how much closer an element move towards/away from you, it will still appear to be of the same size unlike in real world. (More information on perspective).

We'll set this property on the parent container of our cube so that all its children (faces) are affected by a common perspective, like so:

.cube {
  width: $size;
  height: $size;
  position: relative;
  
  perspective: 600px;
}

As expected, now the front face does appear bigger in size. But still something is missing.

transform-style

Even after we give perspective to our scene, we still have an issue. The front face should ideally appear above all other faces, hiding them behind it. But it's not.

The reason is that our cube container has no 3D rendering context which is defined as follows on CSSWG:

A containing block hierarchy of one or more levels, instantiated by elements with a computed value for the ‘transform-style’ property of ‘preserve-3d’, whose elements share a common three-dimensional coordinate system.

Without an element having the transform-style property set as preserve-3d, its children are rendered as flattened, having no stacking context. Thus even when we bought the front face closer on Z-axis, it continued to render it at its original z-index with no consideration to its position in the 3D space.

Try to give the .cube element this property and see what happens.

.cube {
  width: $size;
  height: $size;
  position: relative;
  
  perspective: 600px;
  transform-style: preserve-3d;
}

It worked. Now that we have our 3D system setup, let transform this into a cube!

Positioning the faces

We'll take one face at a time and place it at its appropriate position. First let's understand the coordinate system in CSS. If we were to take one of our cube face, it is something like this:


Front face lying flat with Z-axis coming towards us

As you can see, the Z-axis is coming out of the screen straight from the element. Hence, when we translate the front face positively on the Z-axis, it appears closer to us. A point to note here is that this coordinate system is local to this element. Let's look at that more closely.

We'll use our front face again and rotate it a bit about its Y-axis.

.cube-face-front {
  transform: rotateY(40deg);
}

Now this is how our front face looks like after the rotation:


Axes rotate with the face.

Note how the axes rotated along with element. This means that Z-axis is no longer coming straight towards us. Instead it is in the direction of the element. So if were to move it along the Z-axis now, it would move in the direction in which the element is facing.

This is the concept we'll be using to position every face of our cube. Assume that the center of the cube is in the 2D place of the screen. The cube faces then need to be positioned around it in the 3D space. Remember, We rotate the face so that it faces the required direction then translate on Z-axis.

Front face

Nothing to rotate here. Simple move the front face forward by half the cube's length.

.cube-face-front {
  transform: translate3d(0, 0, $size/2);
}

Back face

The back face will face in the opposite direction to the front one. Which means it needs to be rotated by 180 degrees about the Y-axis before translating like so:

.cube-face-back {
  transform: rotateY(180deg) translate3d(0, 0, $size/2);
}

2 sides done. 4 more to go.

Left face

In case you still have doubt how the transformation are being done, lets understand this face through some visuals.

This is how the left face is right now, lying flat in the 2D plane (z=0):


Left face: in the 2d plane

As the left side needs to face towards the left, we give it a rotation of 90 degrees:


Left face: after rotation

And as with every face, we move it on its Z-axis:


Left face: after translation

This is final CSS for left face:

.cube-face-left {
  transform: rotateY(-90deg) translate3d(0, 0, $size/2);
}

Right face

This is similar to the left face, except for a positive rotation:

.cube-face-right {
  transform: rotateY(90deg) translate3d(0, 0, $size/2);
}

Top face

This face needs to be rotated about the X-axis by 90 degrees so that it faces upwards:

.cube-face-top {
  transform: rotateX(90deg) translate3d(0, 0, $size/2);
}

Bottom face

Similarly, by giving a negative rotation we position the bottom face:

.cube-face-bottom {
  transform: rotateX(-90deg) translate3d(0, 0, $size/2);
}

This completes the positioning of the faces and now we have our cube complete with the following final CSS (I have also added random colors to each face to differentiate them):

$size: 150px; // cube length
.cube {
  margin: 100px;
  width: $size;
  height: $size;
  position: relative;
  
  perspective: 600px;
  transform-style: preserve-3d;
}
.cube-face {
  width: inherit;
  height: inherit;
  position: absolute;
  background: red;
  opacity: 0.8;
}
.cube-face-front {
  background: yellow;
  transform: translate3d(0, 0, $size/2);
} 
.cube-face-back {
  background: orange;
  transform: rotateY(180deg) translate3d(0, 0, $size/2);
} 
.cube-face-left {
  background: green;
  transform: rotateY(-90deg) translate3d(0, 0, $size/2);
} 
.cube-face-right {
  background: magenta;
  transform: rotateY(90deg) translate3d(0, 0, $size/2);
} 
.cube-face-top {
  background: blue;
  transform: rotateX(90deg) translate3d(0, 0, $size/2);
} 
.cube-face-bottom {
  background: red;
  transform: rotateX(-90deg) translate3d(0, 0, $size/2);
}

Now to rotate the cube we can simply apply rotations on the .cube element. Try giving it a rotation of 180 degrees around Y-axis (vertically):

.cube {
  margin: 100px;
  width: $size;
  height: $size;
  position: relative;
  
  perspective: 600px;
  transform-style: preserve-3d;
  transform: rotateY(180deg);
}

You should have something like:

See the Pen 3d cube gallery tutorial - P2 by Kushagra Gour (@chinchang) on CodePen

Do you notice something wrong? We turned the cube 180 degrees around its vertical axis. We should have seen the back face instead of the front face now. We do see it, but it is showing smaller for some reason. What did we do wrong?

Fixing the perspective

If you remember, we gave the perspective property to the cube container (.cube). And when we rotated that element just now, the perspective marked by the vanishing point also rotated along with it. So the vanishing point which was initially somewhere behind the 2D place came in front of the 2D plane after the rotation, causing the issue.

What we ideally want is that the perspective never changes and remains constant no matter what element we transform.

How do we fix this? We wrap all our elements with another DIV to which give the perspective property:

<div class="scene">
  <div class="cube">
    <div class="cube-face  cube-face-front"></div>
    <div class="cube-face  cube-face-back"></div>
    <div class="cube-face  cube-face-left"></div>
    <div class="cube-face  cube-face-right"></div>
    <div class="cube-face  cube-face-top"></div>
    <div class="cube-face  cube-face-bottom"></div>
  </div>
</div>
.scene {
  margin: 100px;
  width: $size;
  height: $size;
  
  perspective: 600px;
}
.cube {
  position: relative;
  width: inherit;
  height: inherit;
  
  transform-style: preserve-3d;
  transform: rotateY(180deg);
}

Check the result now and everything should appear as expected.

Try giving it different rotations like transform: rotateX(30deg) rotateY(30deg) to play with it little. Once done, remove the transform property.

Adding interactivity

We now add some controls to navigate through the gallery. For this we are going to use a nice trick called the Checkbox Hack. Though we'll be using radio buttons (as only one will be selected at a time) instead of checkboxes, the concept remains the same. You can read more about the Checkbox Hack in Chris Coyier's article.

Not going in much depth we add the radio buttons to our HTML:

<!-- CONTROLS -->      
<input type="radio" checked id="radio-front" name="select-face"/>    
<input type="radio" id="radio-back" name="select-face"/>
<input type="radio" id="radio-left" name="select-face"/>
<input type="radio" id="radio-right" name="select-face"/>
<input type="radio" id="radio-top" name="select-face"/>
<input type="radio" id="radio-bottom" name="select-face"/>
<div class="scene">
  <div class="cube">
    <div class="cube-face  cube-face-front"></div>
    <div class="cube-face  cube-face-back"></div>
    <div class="cube-face  cube-face-left"></div>
    <div class="cube-face  cube-face-right"></div>
    <div class="cube-face  cube-face-top"></div>
    <div class="cube-face  cube-face-bottom"></div>
  </div>
</div>

and following CSS to bind the cube's rotation with the radio buttons:

#radio-back:checked ~ .scene .cube {
  transform: rotateY(180deg);
} 
#radio-left:checked ~ .scene .cube {
  transform: rotateY(90deg);
} 
#radio-right:checked ~ .scene .cube {
  transform: rotateY(-90deg);
}
#radio-top:checked ~ .scene .cube {
  transform: rotateX(-90deg);
}  
#radio-bottom:checked ~ .scene .cube {
  transform: rotateX(90deg);
}

In the above CSS we simply state when each radio button is checked, what should be the rotation of the cube at that time.

Final Code

To make it more pleasing, we add some transition effect to the cube and proper alignment to get the following code:

<!-- CONTROLS -->
<input type="radio" checked id="radio-front" name="select-face"/>    
<input type="radio" id="radio-left" name="select-face"/>
<input type="radio" id="radio-right" name="select-face"/>
<input type="radio" id="radio-top" name="select-face"/>
<input type="radio" id="radio-bottom" name="select-face"/>
<input type="radio" id="radio-back" name="select-face"/>

<div></div><!-- separator -->

<div class="scene">
  <div class="cube">
      <div class="cube-face  cube-face-front"></div>
      <div class="cube-face  cube-face-back"></div>
      <div class="cube-face  cube-face-left"></div>
      <div class="cube-face  cube-face-right"></div>
      <div class="cube-face  cube-face-top"></div>
      <div class="cube-face  cube-face-bottom"></div>
   </div>
</div>
$size: 150px; // cube length
body {
  text-align: center;
  padding: 50px;
} 
.scene {
  display: inline-block;
  margin-top: 50px;
  width: $size;
  height: $size;
  
  perspective: 600px;
}
.cube {
  position: relative;
  width: inherit;
  height: inherit;
  
  transform-style: preserve-3d;
  transition: transform 0.6s;
}
.cube-face {
  width: inherit;
  height: inherit;
  position: absolute;
  background: red;
  opacity: 0.8;
}
// faces
.cube-face-front {
  background: yellow;
  transform: translate3d(0, 0, $size/2);
}  
.cube-face-back {
  background: black;
  transform: rotateY(180deg) translate3d(0, 0, $size/2);
} 
.cube-face-left {
  background: green;
  transform: rotateY(-90deg) translate3d(0, 0, $size/2);
} 
.cube-face-right {
  background: magenta;
  transform: rotateY(90deg) translate3d(0, 0, $size/2);
} 
.cube-face-top {
  background: blue;
  transform: rotateX(90deg) translate3d(0, 0, $size/2);
} 
.cube-face-bottom {
  background: red;
  transform: rotateX(-90deg) translate3d(0, 0, $size/2);
}  
// controls 
#radio-back:checked ~ .scene .cube {
  transform: rotateY(180deg); 
} 
#radio-left:checked ~ .scene .cube {
  transform: rotateY(90deg); 
} 
#radio-right:checked ~ .scene .cube {
  transform: rotateY(-90deg); 
}
#radio-top:checked ~ .scene .cube {
  transform: rotateX(-90deg); 
}  
#radio-bottom:checked ~ .scene .cube {
  transform: rotateX(90deg); 
}

Adding some background-image to all the faces we get the final result:

See the Pen 3D cube image gallery by Kushagra Gour (@chinchang) on CodePen

Hope you enjoyed this 3D ride and create some really amazing things using it!

Comments

  1. Very nice, similar to something I played around with a few months ago: http://jonmann20.github.io/playground/breakdancing-cube

    • Chetan
      Permalink to comment#

      please send me the code for this trick thanks..!!
      (lohadechetan009@hotmail.com)

    • Daren
      Permalink to comment#

      Great cube!

      Question #1: I’ve been playing around with your code and can’t seem to be able to put all of the radio buttons in a DIV. After putting them in a div with an id=”options”, the cube stops working. How does the css need to change to make this work?

      Question #2: When taking a screen capture of your cube (using Windows “snipping tool”), I find that your cube is actually 288px by 288px!…??? But your code has it set to 250px by 250px. What’s up with that?

  2. Permalink to comment#

    I just did some 3D craziness by building a small New York City on one of my own sites last week. Check it out here.

  3. Scott
    Permalink to comment#

    This doesn’t work at all in Chrome. I just see a flat image that disappears randomly.

    • Permalink to comment#

      I’m using Chrome (v 28.0.1500.95 on Mac) and it works fine for me.

    • Permalink to comment#

      Chrome v 28.0.1500.95 m (windows) hasn’t fixed its bug with ‘transform-style: preserve-3d;’ or maybe we are doing something wrong ¡¡¿¿??!!

      My old 3D cube doesn’t work in Chrome, in FF is all right

    • Roko
      Permalink to comment#

      Try to use a new mixin rule to create -webkit-transition and other compatibles browsers

  4. dan
    Permalink to comment#

    Cool trick and very smooth. I suppose today is a great day to learn about SCSS!

  5. So I am guessing this would not work in IE10 and below since they do not support the “preserve-3d” property. Anyone know the answer for certain?

  6. Alex
    Permalink to comment#

    Awesome. Thanks

  7. Jonathan Cardoso [JCM]
    Permalink to comment#

    I’ve done a logo using a similar cube some time ago: http://codepen.io/JCMais/pen/lByim

  8. Ryan Boone
    Permalink to comment#

    Great article. I think this could be the most straightforward way I’ve ever had this explained to me. My only criticism is that the left-face rotation graphics were confusing to me. They all look exactly the same. It could just be me. I’ve been known to be slow at times.

    Other than that, fantastic article! Thank you for helping me to understanding the black magic that is CSS 3D. My head is spinning with ideas.

    • Ryan Boone
      Permalink to comment#

      Ah, after hacking around in dev tools, I see that the picture for the left face translation is wrong. They should be tut4.png and tut5.png. Right now all three are tut3.png.

    • oooops fixed.

  9. blwoosky
    Permalink to comment#

    hey chris ,this article has a few mistakes,
    when you set each face ,it’s all .cube-face-left in SCSS code,
    I bevelieve you can understand what i want to say ,

  10. Nicky
    Permalink to comment#

    I am just stunned you can do this all in CSS.
    Amazing – well done.

  11. Permalink to comment#

    I might be going a bit overboard with this dodecahedron using CSS.

  12. Ryan Boone
    Permalink to comment#

    Now, my question is this: How do you create a 3D object with rounded corners? I’ve seen a 3D iPhone created in CSS using pseudo-elements, but I’m trying to figure out how only two planes can create the rounded look. Is it the gradient applied? It seems like the illusion would break at certain angles. I would imagine applying dynamic light would reveal it fairly easily. I can see how creating more complex objects is a bit of a mountain to scale.

    • I don’t think rounded surfaces are possible yet. Even in the example you mentioned, the rounded surfaces are created using small planes aligned at corners of the strip which joins the 2 faces of the phone.

  13. Is there any benefit to using top, left, right, top, bottom, front, and back classes instead of nth-child pseudo classes? Since you’re using a CSS preprocessor which gives you access to programatical functions like loops, seems like nth-children would be easier.

    • Depends on your use case. If you would like to target specific sides (say you want to put the best image on front face), you got to give those classes. Of course you could do that with nth-child too, but classes just add semantics.

  14. Nice tutorial and now I am going to try it :)

  15. Varsha
    Permalink to comment#

    its amazing

  16. Permalink to comment#

    That’s sweet but complicated!

  17. As ever an awesome post / tutorial guys.

  18. Joshua Dixon
    Permalink to comment#

    Hey, great tutorial. I really learned a lot. However, whenever I attempt this, the front face does not appear correctly. It looks like it is center aligned inside of the cube, instead of on the outside of the cube like the rest of the faces. Any idea what I might do about that? I’ve tried manipulating the translate3d values, but no luck.

  19. Joshua Dixon
    Permalink to comment#

    Sure, here it is. And thank you very much for responding so quickly!

    http://cdpn.io/cCHdk

    • Its the following comment what is causing issue:
      // faces

      Its valid in SASS but not in CSS (which you are using in your pen). If you want to use CSS, change it to:
      /* faces */

  20. echo
    Permalink to comment#

    My favorite example, I’ve saved this link as I could no longer find where it’s linked from.

  21. Phil
    Permalink to comment#

    Why when changing from showing the right face to the back face, does the cube spin all the way around, rather than spinning one face round?

    • That depends on the angle you are setting to reach the back face. Like if you are at an angle of 90deg, to go one face ahead, you could set either 180deg (+90) or -180deg (-270)…latter will spin the cube all the way round.

  22. gorgy
    Permalink to comment#

    its a pretty nice design i love it, but i have been trying all i could to learn how it was designed with the come and probably if it could be done on my system too,but i can’t get it yet, so please how do i go about it?

This comment thread is closed. If you have important information to share, you can always contact me.

*May or may not contain any actual "CSS" or "Tricks".