There are two parts to this.
- The first one is about building the 3D shape.
- The second one involves animating it (and this is the simple part).
I'll go through each one of them in detail, but first I'll briefly answer the three questions you've asked at the end.
is this css3 or html5 or both?
It's CSS3. CSS 3D transforms and keyframe animations.
how much javascript do I need?
Well, if you don't want to create 3D shape itself with JavaScript, then you don't need any JavaScript to animate it. Any browser that supports 3D transforms also supports keyframe animations and, if a browser supports keyframe animations, then you probably want to use those instead of jQuery (and maybe even your own custom JS) animations.
which browsers support this?
Things didn't look good two years ago, but they're getting better...
✓ Chrome, Safari, Firefox (though Firefox 3D animations are jiggly and not nice to my CPU... I do have a 6-year-old old laptop, it's true) Opera 15+ (WebKit) support 3D transforms. And of course keyframe animations.
Firefox supports everything unprefixed, Chrome/Safari/Opera need the -webkit-
prefix - update: Chrome 36+ and Opera 23+ support them unprefixed as well, so it's only Safari left now. I won't be using any prefixes in the answer or in the demo (-prefix-free
takes care of things there). Another update: Safari 9 unprefixes transforms.
✗ 3D transforms were not supported by Opera before switching to WebKit.
✗ IE up to and including 9 don't support 3D transforms. IE10 and IE11 support 3D transforms, but do not support nesting of 3D transformed elements (cannot create realistic looking 3D shapes via applying 3D transforms on both parent and child elements, as 3D transformed children of a 3D transformed parent get flattened into the plane of the parent). Update: Edge now supports nesting of 3D transformed elements.
Alright, I hope that clears a few things. Now...
1 Building a CSS cube.
1.2 What is a cube and starting off with the HTML
First of all, try to picture a cube. Maybe this link helps? Or let's have an image here as well.
It has 6 square faces. 1 top, 1 bottom; 1 in front, 1 in the back; 1 left, 1 right. This means that the HTML is simply:
<ul class='cube'>
<li class='face'></li>
<li class='face'></li>
<li class='face'></li>
<li class='face'></li>
<li class='face'></li>
<li class='face'></li>
</ul>
You first put some regularity into the faces, give them equal width
and height
, position them absolutely so that they're all stacked one on top of the other, give them different backgrounds, they don't mind that. You can give them some padding, put some dummy text into them, whatever...
Now comes the interesting part: moving them so that they form a cube in space. You do that using 3D transforms.
1.2 The Coordinate System
Consider this 3D coordinate system:
Initially, all the 6 faces are all exactly where you see the blue square, in the xOy
plane (where O
is the intersection of the 3 axes).
Pay attention to the direction of the y-axis
. Coming from a mathematical background, this seemed a bit weird to me at first, but this is how the coordinate system is for the screen.
In 2D, the origin O
is at the top left corner, so the +
(positive direction) of the x-axis
points right and the +
of the y-axis
points down.
1.2.a Translations along the axes
So a translation of a positive value along the x-axis
(for example, translateX(10px)
) moves the element to which it is applied to the right (towards the +
of the x-axis
), while a translation of a negative value along the x-axis
(something like translateX(-10px)
) moves it to the left.
Similarly, a translation of a positive value along the y-axis
(like translateY(10px)
) moves the element down (towards the +
of the y-axis
), while a translation of a negative value along the y-axis
(like translateY(-10px)
) moves it up.
Now add another dimension. With the z-axis
. The +
(positive direction) of the z-axis
comes out of the screen, towards you. So a translation of a positive value along the z-axis
(like translateZ(10px)
) moves the element forward (towards the +
of the z-axis
and towards you), while a translation of a negative value along the z-axis
(like translateZ(-10px)
) moves it backward (away from you).
1.2.b Rotations around the axes
Rotations of positive angle values (for example, rotate(15deg)
- note that if the rotation axis is not specified, then it is assumed to be the z-axis
) in CSS are clockwise and rotations of negative angle values (like rotate(-15deg)
) are counter-clockwise.
And clockwise means clockwise as seen from the +
of the axis around which you rotate the element.
So a positive rotation around the x-axis
means a clockwise rotation in the yOz plane
as seen from the +
of the x-axis
, which is at the right.
A positive rotation around the y-axis
means a clockwise rotation in the zOx plane
(the horizontal plane) as seen from the +
of the y-axis
, which is at the bottom.
A positive rotation around the z-axis
means a clockwise rotation in the xOy plane
(the plane of the screen) as seen from the +
of the z-axis
, which is how you naturally see the screen.
1.3 Put the faces in the right positions in order to form the cube
1.3.1 Put one face at the front
This means translating it forwards (in the positive direction) along the z-axis
. What is this? A translateZ
of a positive value. What value? Well, it should be half the width
(or the height
, doesn't matter, it's a square, they're equal).
Suppose I have the width: 16em;
Then in this case, you translate the face forward (along the positive z-axis
) by 16em/2 = 8em
. In CSS, that's
.face:nth-child(1) { transform: translateZ(8em); }
Note: The translate transform
moves the entire coordinate system of the element that is translated (and consequently, the transform-origin
for any subsequent transforms).
1.3.2 Put the second face at the back
That's simple, right? Just a translate along the z-axis
, by the same value in the opposite direction, right? .face:nth-child(2) { transform: translateZ(
-8em
); }
, right?
Well... actually... only if you don't want to put any content on that face. Or if you don't want to have as a background an image for which it matters which is left and which is right.
Each of these squares that make up the cube has a front and a back. The front is the one towards the positive direction of the z-axis
; the one that "looks at you from the computer screen". If you put text there, it flows normally on the front. But it looks vertically mirrored on the back.
That's why the first thing that you should do is to rotate the second square face by 180° around the vertical axis (y-axis
). After doing that, you can then translate this second square face along the z-axis
in order to move it to the back.
The translate value is again positive in this case. Just like the translate transform
moves the coordinate system of the element that is translated, the rotate transform
... well... rotates it. This means that after rotateY(180deg)
is applied, the +
of the z-axis
points towards the back (not towards the front anymore).
So the CSS that rotates and then translates the second face into its position on the cube is:
.face:nth-child(2) { transform: rotateY(180deg) translateZ(8em); }
Note: the cube is a really simple 3D shape, but one CSS property that I find really useful to check whether I've rotated faces the right way is backface-visibility
. If I set it to hidden
and I don't see the rotated element, it means that I'm looking at it from the back.
1.3.3 Put the third face to the right
First of all, its front has to "look" towards the right. This means that it has to be rotated around the y-axis
so that the +
of the z-axis
ends up pointing towards the right and then it has to be translated along the positive z-axis
by the same positive value (8em
in this case) that is half the length of the side of the square.
Rotated by what angle? Well, 90°, which means the CSS needed is:
.face:nth-child(3) { transform: rotateY(90deg) translateZ(8em); }
1.3.4 Put the fourth face to the left
First rotate it by 90°
, but the other way, to make its front "look" towards the left. "The other way" means rotateY(-90deg)
, then apply the same old translateZ(8em)
. In one CSS line:
.face:nth-child(4) { transform: rotateY(-90deg) translateZ(8em); }
1.3.5 Put the fifth face at the top
First rotate it by 90°
around the x-axis
and then translate along the z-axis
(which points up after the rotation). CSS:
.face:nth-child(5) { transform: rotateX(90deg) translateZ(8em); }
1.3.6 Put the sixth (and last!) face at the top
Rotate it by 90°
the other way around the x-axis
, then translate it along the z-axis
(which points down after the rotation). CSS:
.face:nth-child(6) { transform: rotateX(-90deg) translateZ(8em); }
1.4 Perspective and realistic looking 3D shapes
All the faces of the cube are now in place. But this won't look like much of a 3D shape unless the faces that are closer seem bigger than those that are further away. The CSS property that takes care of this is called perspective
and is applied on the parent of the elements which have 3D transforms applied on them.
It still won't look like much of a 3D shape if you look perpendicular to the center of the front face because you won't see any of the other faces. To fix this, you need to rotate the cube itself in 3D. And this is why it's important that the browser allows nesting of 3D transformed elements.
You enable nesting of 3D transformed elements with transform-style: preserve-3d;
on the parent element (.cube
in this case). Unfortunately, IE10/11 do not support the preserve-3d
value for the transform-style
property (only support the flat
value), so you cannot have a realistic looking cube in IE yet (unless you take the 3D transforms applied on the cube and chain them before the transforms applied for each face, which means one set of keyframes for each face if you want to animate the cube as a whole).
Update: transform-style: preserve-3d
now landing in IE as well.
Note: If you also apply 3D transforms on the .cube
, then you should move the perspective
property on the parent of the .cube
.
2 Animating the CSS cube
That's done using just the regular keyframe animations for the .cube
element. Let's say you want to rotate it:
@keyframes ani { to { transform: rotate3d(5, 12, 13, 360deg); } }
You can have multiple keyframes, with rotations around different axes, but this is the basic idea.
TL;DR
Just give me the damn demo! Alright, here it is:
simple rotating cube demo
And a few more demos I've done recently and which show how to create other slightly more complex 3D shapes:
❆ square antiprism
❆ cuboctahedron
❆ rhombicuboctahedron
❆ rhombic dodecahedron
❆ octahedron
❆ truncated cube