Menu Search
Jump to the content X X
Smashing Conf San Francisco

We use ad-blockers as well, you know. We gotta keep those servers running though. Did you know that we publish useful books and run friendly conferences — crafted for pros like yourself? E.g. upcoming SmashingConf San Francisco, dedicated to smart front-end techniques and design patterns.

Front-End Challenge Accepted: CSS 3D Cube

Do you like challenges? Are you willing to take on a task that you’ve never come across before, and do it under a deadline? What if, in carrying out the task, you encounter a problem that appears unsolvable? I want to share my experience of using CSS 3D effects for the first time in a real project and to inspire you to take on challenges.

It was an ordinary day when Eugene, a manager at CreativePeople1, wrote to me. He sent me a video and explained that he was developing a concept for a new project and was wondering if it was possible for me to develop something like what was in the video.

It was a 3D object (a cuboid, to be precise) that rotated around one of the axes. I already had some experience with working with CSS 3D, and a solution started to form in my mind. I Googled keywords like “CSS 3D cube” to confirm my ideas and answered Eugene that it was possible.

Eugene’s next question was whether I would take on the project? I like tricky tasks, so I couldn’t refuse. At the time, I didn’t realize what I was getting into, but I was beyond determined.

Sharpen Your Axes Link

Let’s remind ourselves about axes — not war axes, but the number lines2, the same axes as in the three-dimensional Cartesian coordinate system that we studied in school. As Wikipedia tells us3:

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 for each axis.

The picture below shows how the axes are oriented in a web browser.

A right-handed three-dimensional Cartesian coordinate system with the Z-axis pointing towards the viewer.4
A right-handed three-dimensional Cartesian coordinate system with the z-axis pointing towards the viewer. (Image: Wikimedia Commons5) (View large version6)

The x-axis is horizontal, the y-axis is vertical, and the z-axis appears to come out from the screen towards you. The z-axis’ zero value is the plane of the screen. Remember this.

Clearing Up The Perspective Link

To create a 3D object, I needed an element (let’s call it a “scene”) with a perspective. The perspective is the depth of the scene, and it depends on the sizes of the objects it contains.

.scene {
  perspective: 800px;
}

If the perspective is too small, objects could get distorted. If it is too big, the 3D effect will be reduced to nothing.

See the Pen jqgMvL7 by Anna Selezniova (@askd43393633292623201714118) on CodePen44403734302724211815129.

Furthermore, there is only one angle of view for all objects in the scene. And the 3D effect depends on the viewpoint’s position.

See the Pen oxKzKv10 by Anna Selezniova (@askd43393633292623201714118) on CodePen44403734302724211815129.

So, how do we calculate the perspective? I discovered that it depends on the axis of rotation. For the x-axis, the height value multiplied by 4 would fit. For the y-axis, it would be the width value multiplied by 4. Here is my magic formula:

const perspective = dimension * 4;

Considered From All Sides Link

After determining the perspective, I started to create a 3D object. I chose a cube because it is straightforward and predictable. A cube element is created as a regular div, relatively positioned, with the width and height defined (say, 200px). It transforms into a 3D object through the transform-style property with a value of preserve-3d. It tells the browser to render all nested elements according to the rules of the 3D world.

In my case, the cube has six divs (or “sides”), absolutely positioned. The class names correspond to the initial positions of the sides (back, left, right, top, bottom, front). Here is the markup:

<div class="scene">
  <div class="cube">
    <div class="side back"></div>
    <div class="side left"></div>
    <div class="side right"></div>
    <div class="side top"></div>
    <div class="side bottom"></div>
    <div class="side front"></div>
  </div>
</div>

By default, all of the sides will be on one plane. So, I needed to rearrange them. Here is how that looks:

See the Pen mPNwPx13 by Anna Selezniova (@askd43393633292623201714118) on CodePen44403734302724211815129.

And here is the resulting CSS:

.cube {
  position:relative;
  width: 200px;
  height: 200px;
  transform-style: preserve-3d;
}
.side {
  position: absolute;
  width: 200px;
  height: 200px;
}
.back {
  transform: translateZ(-100px);
}
.left {
  transform: translateX(-100px) rotateY(90deg);
}
.right {
  transform: translateX(100px) rotateY(90deg);
}
.top {
  transform: translateY(-100px) rotateX(90deg);
}
.bottom {
  transform: translateY(100px) rotateX(90deg);
}
.front {
  transform: translateZ(100px);
}

To rotate the cube, I set the transform property on the cube element to an arbitrary rotational angle along the x-axis:

.cube {
  transform: rotateX(42deg);
}

Overcoming The Shortcomings Link

According to the assignment, I was to rotate the cube only along the x-axis, so I didn’t need the left or right side. I added captions to align with the initial positions of the remaining sides.

I started to rotate the cube and found that the captions on the bottom and back sides were displayed upside down:

See the Pen GZVvMR16 by Anna Selezniova (@askd43393633292623201714118) on CodePen44403734302724211815129.

To solve this problem, I rotated each of these sides along the x-axis by 180 degrees:

.back {
  transform: translateZ(-100px) rotateX(180deg);
}
.bottom {
  transform: translateY(100px) rotateX(270deg);
}

Going Beyond The Screen Link

I started to fill the sides with real content and immediately encountered another problem. I needed to display 1-pixel dotted lines, but they were blurry and looked bad.

See the Pen VjeBPg19 by Anna Selezniova (@askd43393633292623201714118) on CodePen44403734302724211815129.

I soon realized what the problem was. Do you remember that 3D TV ad in which the image extends beyond the screen? It was something like that with my cube.

If you could look at the cube from the left or right side, you would see that its center was on the plane of the screen (zero on the z-axis) and that the front side was beyond the screen. Therefore, it increased visually and blurred.

See the Pen WwVEMR22 by Anna Selezniova (@askd43393633292623201714118) on CodePen44403734302724211815129.

To solve this problem, I shifted the cube along the z-axis to align the front side to the plane of the screen:

.cube {
  transform:translateZ(-100px);
}

Here is the cube now, almost ready:

See the Pen Xdvery25 by Anna Selezniova (@askd43393633292623201714118) on CodePen44403734302724211815129.

Using The Magic Numbers Link

I guess you’ve noticed that I’m using the magic number 100 to shift the sides along the axis. The value 100 is exactly half the height of my test cube. Why half the height? Because that would be the radius of a circle inscribed in a side of the cube (which is a square, apparently).

const offset = dimension / 2;

If I needed to rotate a triangular prism, the circle would be inscribed in a triangle. In this case, the formula for the offset would be as follows:

const offset = dimension / (2 * Math.sqrt(3));

Blowing Away The Cube Link

To consider the task complete, I had to test the result in different browsers.

The picture I saw in Internet Explorer plunged me into depression. To get an idea of what I’m talking about, look at the demo below in your favorite browser. I changed one property that resulted in the cube displaying incorrectly in Internet Explorer. However, don’t peek at the source code until you have read the paragraph under the demo below.

See the Pen XKWMwV28 by Anna Selezniova (@askd43393633292623201714118) on CodePen44403734302724211815129.

The fact is that Internet Explorer doesn’t support the transform-style property with a value of preserve-3d. I learned about this by looking at my trusty resource Can I Use31 (see note 1). In the demo above, I replaced preserve-3d with flat. Did you already know that? Hey, I told you not to peek!

I was upset, but I wasn’t going to give up. A problem is an opportunity to learn something new. Besides, I had accepted the challenge.

Searching For The Fulcrum Link

I was looking for a way to create a 3D object without using transform-style: preserve-3d, and I eventually discovered a useful property: transform-origin. It determines the central point of an element’s transformation. I have created an interactive demo below, which will help you understand how it works:

See the Pen rLNmBp32 by Anna Selezniova (@askd43393633292623201714118) on CodePen44403734302724211815129.

The 3D rotation of the element in the demo is very similar to the front side of a cube, isn’t it? That’s what I used.

(By the way, did you try to select the checkbox backface-visibility: hidden during the 3D rotation? This property is used to hide the back of the element during the 3D transformation.)

Starting All Over Again Link

I started to redo the cube. I didn’t have to interact with the scene as a whole, so I removed the perspective property of the scene element and added it to each 3D transformation, so that now every element transforms independently. Also, I set new properties for each side: transform-origin with a value equal to the position of the cube’s center, and backface-visibility: hidden. Here is how the styles have changed:

.scene {

}
.cube {
  position: relative;
  width: 200px;
  height: 200px;
  transform: perspective(800px) translateZ(-100px);
}
.side {
  position: absolute;
  transform-origin: 50% 50% -100px;
  backface-visibility: hidden;
}

I had to put the sides in the right places. Because of the transform-origin property, I didn’t need to shift them, only rotate them around the axes. It’s like magic! Let’s see how it looks:

See the Pen zBYwEm35 by Anna Selezniova (@askd43393633292623201714118) on CodePen44403734302724211815129.

Here is the CSS for the placement of the sides:

.back {
  transform: perspective(800px) rotateY(180deg);
}
.top {
  transform: perspective(800px) rotateX(90deg);
}
.bottom {
  transform: perspective(800px) rotateX(-90deg);
}
.front {
  transform: perspective(800px);
}

And here you can see the new cube in action:

See the Pen wWvdXd38 by Anna Selezniova (@askd43393633292623201714118) on CodePen44403734302724211815129.

Giving Back To Caesar What Is Caesar’s Link

The second cube looks and spins the same as the first. But in this case, you need to transform each side individually. This might not be very easy, especially if you want to control the intermediate angle of rotation.

Furthermore, if you open the demo in Chrome, you will see that the sides flash during rotation — very frustrating.

In the end, I applied both approaches using the simple test41 of transform-style: preserve-3d. The first cube is the default one. The second cube is for Internet Explorer and browsers that do not support preserve-3d.

Using The Power Of Math Link

Finally, I had to implement a parallax effect. Usually, this effect responds to the user’s action, whether the position of the mouse cursor or the scroll bar. In this case, the effect depends on the angle of rotation.

See the Pen QENyqm42 by Anna Selezniova (@askd43393633292623201714118) on CodePen44403734302724211815129.

So, what data do I have? First, I had the start and end points of the caption’s position or, to put it simply, its offset up and down from the center of a side. Secondly, I had the angle of rotation of the cube.

I spent hours trying to develop a formula. Then, it dawned on me. Here’s what came to mind:

Graphs of the sine and cosine functions45
Graphs of the sine and cosine functions (Image: Wikimedia Commons46) (View large version47)

With the help of sines and cosines, I easily calculated the offset of each caption according to the angle. Here are the formulas I came up with:

const front_offset = offset * sin(angle) * -1;
const bottom_offset = offset * cos(angle);
const back_offset = offset * sin(angle);
const top_offset = offset * cos(angle) * -1;

Summing Up Link

The assignment is now complete, and I can enjoy the result and share it with you. See for yourself48 how it works. Use the scroll or arrow keys to rotate the promo block. Also, try to pull the black triangle on the right up and down to control the angle of rotation manually (unfortunately, this feature does not work in Internet Explorer). Looks pretty good, doesn’t it? And performance is rather high (about 60 frames per second).

I am very glad to have participated in the development of this website. I have gained useful experience in working with CSS 3D and have discovered many interesting properties. More importantly, I have learned that one should never give up; most likely you will find a way to accomplish the task.

I hope you have enjoyed my story and are now ready to take on new challenges.

(vf, il, al)

Footnotes Link

  1. 1 http://cpeople.ru
  2. 2 https://en.wikipedia.org/wiki/Number_line
  3. 3 https://en.wikipedia.org/wiki/Cartesian_coordinate_system
  4. 4 https://www.smashingmagazine.com/wp-content/uploads/2016/06/browsers-axes-opt.png
  5. 5 https://commons.wikimedia.org/wiki/File:3D_coordinate_system.svg
  6. 6 https://www.smashingmagazine.com/wp-content/uploads/2016/06/browsers-axes-opt.png
  7. 7 https://codepen.io/askd/pen/jqgMvL/
  8. 8 http://codepen.io/askd
  9. 9 http://codepen.io
  10. 10 https://codepen.io/askd/pen/oxKzKv/
  11. 11 http://codepen.io/askd
  12. 12 http://codepen.io
  13. 13 https://codepen.io/askd/pen/mPNwPx/
  14. 14 http://codepen.io/askd
  15. 15 http://codepen.io
  16. 16 https://codepen.io/askd/pen/GZVvMR/
  17. 17 http://codepen.io/askd
  18. 18 http://codepen.io
  19. 19 https://codepen.io/askd/pen/VjeBPg/
  20. 20 http://codepen.io/askd
  21. 21 http://codepen.io
  22. 22 https://codepen.io/askd/pen/WwVEMR/
  23. 23 http://codepen.io/askd
  24. 24 http://codepen.io
  25. 25 https://codepen.io/askd/pen/Xdvery/
  26. 26 http://codepen.io/askd
  27. 27 http://codepen.io
  28. 28 https://codepen.io/askd/pen/XKWMwV/
  29. 29 http://codepen.io/askd
  30. 30 http://codepen.io
  31. 31 http://caniuse.com/#feat=transforms3d
  32. 32 https://codepen.io/askd/pen/rLNmBp/
  33. 33 http://codepen.io/askd
  34. 34 http://codepen.io
  35. 35 https://codepen.io/askd/pen/zBYwEm/
  36. 36 http://codepen.io/askd
  37. 37 http://codepen.io
  38. 38 https://codepen.io/askd/pen/wWvdXd/
  39. 39 http://codepen.io/askd
  40. 40 http://codepen.io
  41. 41 https://gist.github.com/askd/6a3cf9db98cb1e09127857c8872b54cb
  42. 42 https://codepen.io/askd/pen/QENyqm/
  43. 43 http://codepen.io/askd
  44. 44 http://codepen.io
  45. 45 https://www.smashingmagazine.com/wp-content/uploads/2016/06/sin_cos-large-opt.png
  46. 46 https://commons.wikimedia.org/wiki/File:Sine_cosine_one_period.svg
  47. 47 https://www.smashingmagazine.com/wp-content/uploads/2016/06/sin_cos-large-opt.png
  48. 48 http://rdcm.com/en/

↑ Back to top Tweet itShare on Facebook

Anna is a creative developer and speaker. She has been working on the web since 2000 and prefers tasks at the intersection of design and frontend. She currently works at Evil Martians.

  1. 1

    Alex Bondarev

    July 4, 2016 1:16 pm

    Hey, Anna. I remember seeing you deliver a talk on this. Reading about it after some time is a nice way to refresh it in memory :) Nice techniques, well done. Parallaxy effect is super cool ;)

    6
  2. 3

    EPIC!

    6
  3. 4
  4. 6

    Jonathan Trang

    July 4, 2016 2:42 pm

    Super cool, and very impressive :)

    Thanks for sharing !

    4
  5. 7

    David Schmidt

    July 4, 2016 3:24 pm

    Really really nice!
    I try to implement this for my project.

    In august 2013, I wrote my bachelor thesis with a similar idea for parallaxing and 2.5D.
    “Implementation of Adobe After Effects compositions with standard web techniques”
    But i do not implement the convertion perspective from an object to the camera or rather the projection Matrix.

    It is open an everybody can extend or join this project.
    Project: https://github.com/damasch/afterEffectsToWebsite
    Thesis (german): http://damasch.de/docs/thesis.pdf

    Keep it up!

    1
  6. 8

    This is great dude! Thanks for sharing. :D

    -6
  7. 9

    great article.
    1. how to add an autoplay video to the face of one cube?
    2. how to add interaction with the user to let the user rotate the cube in directions like left to right or top to bottom?

    Thanks

    1
    • 10

      Hey, Chris! Thanks for the questions.

      2. You need to store the index of the active side (1, 2, 3, 4) and to increase/decrease it using arrow keys or the scrollbar.
      For the first cube, it is enough to change the angle: 90 for the active side with the index 2, 180 for the active side with the index 3, etc.
      For the second cube, you need to transform each side independently so you can use the classes. For example, when the active side number is 2, the side with the number 3 has `transform: perspective(800px) rotateX(90deg) rotateY(180deg) rotateZ(180deg)`.
      1. You can use the active/inactive state of the side to play/pause the background video.

      Hope it helps. Good luck ;)

      2
  8. 11

    Raymond Luong

    July 5, 2016 3:34 am

    This is amazing. Thanks so much for sharing!

    0
  9. 12

    amazing work done Anna!
    Its good to see the real work challenges being shared for great learning of others.

    3
  10. 13

    Very good article, explained well and bringing some good ideas which require this technique.

    1
  11. 14

    Very nice!!! I tried start the same two years ago ending up with
    [this](http://www.jmvc.org/demo/divrot.js)
    you gave some inspiration to get back to the challenge :)

    1
  12. 16

    A few notes.

    One, the direction of the y axis for the screen is down, opposite to that in the Wikipedia image. I edited it some three-four years ago to create a version that’s suitable for this context and a version of that edit can be found in this article I did last year.

    Two, the preserve-3d test fails for IE on Windows 10 (but not on other Windows versions). This is because IE shares some code with Edge on Windows 10 and Edge supports preserve-3d. For the same reason, it’s unlikely this problem will get fixed. And yes, I know, who uses IE on Windows 10 when there’s Edge…

    Three, the code could be compacted a lot and also be made more maintainable. I did a quick fork that uses preprocessors and this way reduces the 20 lines of HTML to 5 lines of Haml (to a quarter) and the 159 lines of CSS to 97 lines of SCSS (by 40% and I believe this could be improved at a second pass).

    The first thing I did was of course use variables instead of hard coded values (for the cube edge length, for the animation duration and so on).

    Then I added another element in between the .scene and the .cube. This is a positioner that gets moved, together with all its descendants. It’s shifted back along the z axis – basically, I just set on it the first half of the transform you have on the cube so I don’t have to include that translateZ() for each and every keyframe.

    Then, you can compact something like:

    15% { /* styles */ }
    25% { /* the exact same styles as above */ }

    into something like 15%, 25% { /* styles */ }

    Also, if you don’t specify the 0% or 100% keyframes, they get automatically generated from the values you’ve set for the properties of that element, or, if you haven’t set then, from the defaults. The default value for transform is none and rotateX(0deg) results in exactly the same thing, so we can easily omit the 0% keyframe in this case.

    This means we’re left with this for the cube keyframes:
    15%, 25% { transform: rotateX(90deg); }
    40%, 50% { transform: rotateX(180deg); }
    65%, 75% { transform: rotateX(270deg); }
    90%, 100% { transform: rotateX(360deg); }

    We notice that we can also write it as:

    1*25% - .4*25%, 1*25% { transform: rotateX(1*90deg); }
    2*25% - .4*25%, 2*25% { transform: rotateX(2*90deg); }
    3*25% - .4*25%, 3*25% { transform: rotateX(3*90deg); }
    4*25% - .4*25%, 4*25% { transform: rotateX(4*90deg); }

    This means we can derive a general formula for $i from 1 through 4

    $i*25% - .4*25%, $i*25% { transform: rotateX($i*90deg); }

    and generate the keyframes inside an SCSS loop.

    In the same way, using an SCSS loop, I’ve generated the CSS that positions the four faces, sets the different backgrounds on them as the names of the images follow a pattern and set the text (in this case in an ::after pseudo).

    I also didn’t use a separate element for the guide, but the ::before pseudo with a linear gradient on it.

    I’ve used the same set of keyframes for all the text animations. This set of keyframes was generated in a loop, in the same way as the set of keyframes for the cube. The only thing that differs for each text, depending on the face it’s on, is the animation-delay.

    3
  13. 18

    Nikolay Talanov

    July 6, 2016 2:53 am

    Months ago I made slider based on RD Construction website (the one with rotating 3d cube). Their concept is cool, but their implementation is limited to 4 edges, because it doesn’t uses transform-style: preserve-3d.

    Here is the link – http://codepen.io/suez/pen/WvaKpy

    The only problem with my version (and it’s big one) is the fact that Firefox have critical bug, which hasn’t been fixed for years already, causing wrong z-index order when you are using nested 3d transforms with transform-style: preserve-3d. Original website doesn’t have this problem, because they are using some sort of pseudo-3d, which limits slider to 4 edges.

    2
  14. 19

    This is great! I’ve been tinkering with 3D animations and have had some success with a 3D cube, however, I have been asked to create and animate a rectangle rather than a cube (much to my chagrin).

    Any pointers on how I can apply the same principles to a 310px wide x 180px high rectangle?

    Also — eventually, the 3D rectangle will need to be implemented in a responsive site. Can ems or rems be used as measurements?

    Another caveat — we are not a SASS shop, we use “vanilla” css.

    Any guidance would be appreciated.

    THANKS again for a great lesson!

    2
    • 20

      Thank you, Bosh!
      I’ve been experimenting with the cube in the article, but on a real website I used a flexible cuboid with the screen width and height. So you can use this technique for your case.
      As for the em/rem units, they are well supported and you can use them instead of px in all 3D properties.
      Here’s an example for you: https://codepen.io/askd/pen/OXMzOx
      Good luck ;)

      0
  15. 21

    Thanks for the sharing, Anna! I checked out the RD Construction site on mobile phone and it works too!

    0
  16. 22

    Enoch Griffith

    July 9, 2016 4:22 pm

    That was amazing! I’m just fresh out of programming school and have often wondered about this.

    0
  17. 23

    Thanks Anna, you inspired me to use this on my site

    1
  18. 24

    Thanx for the article but mb it’s a time to create something new? I think I saw the very first talk about this cube on girls-not-bombs in 2014… 2+ years since that time and you still write and discuss this cube :) Just why?

    0
    • 25

      Hi, Kirill!
      You say you’ve seen the talk about the cube in 2014. It’s amazing because I told about this project in summer 2015 ;) The original was in Russian, and now I translated it into English to share with a wider audience.
      Some speakers show the same presentation during the year at several conferences, does this bother you?
      And yes, I created something new. And what about you? ;)

      0
  19. 26

    Hello I check your demo

    http://rdcm.com/en/

    In this i want to rotate cube automatically is there any possibility for that?

    0
  20. 27

    Just cool

    0

↑ Back to top