CSS in 3D

I’ve spent some time combing through every resource I can find on the subject which, surprisingly, wasn’t as complex as I thought it would be. So have a read, play with the examples, and hopefully my work will prove a little value in conveying at least one new thing to you!

*Tiny disclaimer – this article is probably best viewed in Google Chrome as there are a few live examples which won’t work if you’re using Internet Explorer (even the modern ones).  Firefox and Safari should be ok.* 

Setting the stage

3D transformations allow us to take an existing 2D element on our page an manipulate it in a 3D space. That much you probably already knew, it’s pretty intuitive! The 3D space that we move elements around within, which can be defined on the element itself or on a containing element, that bit I find strange. Personally I prefer setting the ‘space’ on a containing element, that way any children we add will all act as you’d expect. If we were to set up each 3D element with it’s own ‘space’, we would end up with a lot of mismatching perspectives or a lot of repetition in trying to keep everything in tune. That may be what you’re after, but I’ll be sticking with a containing element. So let’s create one!

#stage {
  perspective: 1200px;
  perspective-origin: 75% 75%;
}

Fairly simple, we only need to set two properties (in addition to any positioning / size properties to make it fit within your page). perspective sets the distance of the scene from the viewer, like setting the focal length of a camera. perspective-origin changes the position of the vanishing point. It defaults to the middle (50% 50%) but can be set anywhere we want using any of the distance units, percentage, or left / right / top / bottom / center.

Try playing with the example below. I’ve set it up to allow you to change the perspective value using the slider and the perspective-origin with the little circle. You can also drag the cube around – that serves no purpose.


Moving in 3D

Now we have our stage set up, let’s put an element into it:

<div id="stage">
  <div class="element"></div>
</div>

We can affect 3D manipulations through the transform property. This property takes both 2D values (rotate, translate, scale, and skew) and 3D values (rotate, translate, and scale) so there is some overlap, but we’ll focus on the 3D set. In full they are:

  • translateX( 100px )
  • translateY( 100px )
  • translateZ( 100px )
  • scaleX( 1 )
  • scaleY( 2 )
  • scaleZ( 3 )
  • rotateX( 15deg )
  • rotateY( 30deg )
  • rotateZ( 45deg )
  • rotate3d( 1, 2, 3, 45deg )

We also have shorthand versions:

  • translate3d( 100px, 100px, 100px )
  • scale3d( 1, 2, 3 )

You might note that I’ve not put rotate3d in the short hand list – it’s not shorthand for the other 3 rotation values. In fact rotateX, rotateY, and rotateZ act more like short hand for rotate3d but we’ll get to that!

So those are the values which we can apply individually like this: transform:translateX(100px);, but if we add another transform later on in our stylesheet the first one will be overwritten. To apply multiple transforms we have to write them into a single property. In the example below, both panels have the first set of transforms applied to them, but the left panel has a second transform property which overwrites the first set.

.right,
.left{
   transform:
     translateX( 50px )
     translateY( 50px )
     scaleX( 1.5 )
     rotateY(45deg)
     rotateX(10deg);
}
.right {
  transform:scaleY(1);
}

Take note that the order, specifically when rotation is involved, does matter. It’s not as important for scaling and translation but it’s very important for the rotation. In fact, it’s so important I’m going to give it its own section!


Rotation in 3D

When a transform is being applied to an element, each value will be applied in order from left to right. The example below should clearly show this effect (plus I’ve used it to introduce a working example of rotate3d). The two panels to the left have exactly the same rotation values being applied, the only difference is the order.

.one   { transform: rotateY( 45deg ) rotateZ( 90deg ); }
.two   { transform: rotateZ( 90deg ) rotateY( 45deg ); }
.three { transform: rotate3d( 0, 1, 2, 90deg );        }

  • .one The Y rotation is applied first (while the Y axis points up), then the Z rotation is applied (after its axis has been moved 45Deg).
  • .two The Z rotation is applied (while its axis points straight out at us), then the Y rotation is applied (which now points out to the right and as a result makes the face ‘bow’ to us).
  • .three The first three arguments are context dependent vectors that define an axis around which we can rotate the element by the angle defined in the last argument… what?

If you’re like me then those vectors on .three might not make immediate sense, but hopefully the diagram below will clear it up a bit! We define a ‘custom’ axis starting at the origin (which defaults to the middle of our element) and extends out past a point in 3D space. Our 3 vectors are just the (x, y, z) coordinates of that point.

axis vector from origin to x=2, y=3, z=5

So the three individual rotation properties (rotateX Y and Z) are really just short hand for the relevant axis vectors in rotate3d:

rotateX( 90deg ) == rotate3d( 1, 0, 0, 90deg )
rotateY( 90deg ) == rotate3d( 0, 1, 0, 90deg )
rotateZ( 90deg ) == rotate3d( 0, 0, 1, 90deg )

That’s why rotate3d wasn’t in the shorthand list and, if anything, the other three should be!


Changing the origin

We can also set the location of the origin point for each element. This is the point from which any translation (2D or 3D) is calculated. Like the perspective-origin it defaults to the middle but we can move it. Unfortunately we can’t position it in 3D space – we can only move it around the 2d plane of the element in question, but we can achieve the effect of an off-plane origin by using a parent to do atransformand applying a translateZ value to the child. There are some things to be aware of when using this technique, but transforming children has its own section later, have patience!

The (very simple) example below shows different origins in the first two elements. The third was an experiment to see if it was possible to set the origin, translate the element, then move the origin… it’s not, the second origin overwrites the first.

.one {
  transform-origin: top left;
  transform:rotateY(45deg);
}
.two {
  transform-origin: top center;
  transform:rotateY(45deg);
}
.three {
  transform-origin: top right;
  transform:rotateY(45deg);
  transform-origin: top left;
}


Backface visibility

This is a simple but important property. If an element has been flipped we can define whiter the back should be visible (it will show a mirror image of the front) or not.


Transforming children

transform-style Allows us to set how we want transformations to apply to the children of any elements that we are manipulating in 3D. For example, in the Codepen examples above, the text that’s written on the planes are children of elements that have been rotated in 3D. transform-style has not been set for them so they default to flat which means they have no 3D context. They are applied to their parent element in the same way as usual and are then just taken along for the ride when their parent moves in space.

But If we set transform-style: preserve-3d; on an element then its (direct) children get to move in space too! Here’s an example of the difference:

On the left of each element, from the biggest black panel down, has transform-style: flat; and on the right transform-style: preserve-3d; is set. Each child panel on both sides has a ztransformbut, as you can see (unless you’re using IE or some other older browser) only those whose parents have the preserve-3d option get to play.


Conclusion

That’s everything! All the sections you’ve just read through (assuming you didn’t skip them) cover the mechanics of 3D manipulation in CSS. It’s a foundation we should know well but I wouldn’t recommend writing CSS transforms to create anything more than the simplest 3D work – that would be painful! Instead, now would be the time to look into a modelling tool like Trivdiv to build, and possibly a simple lighting engine like Photon (which Trivdiv actually uses) to light your models. If, however, you are looking to take your 3D CSS world into the realms of disbelief, I highly recommend reading Keith Clark’s article on building an environment with textures, simple shading and shadows. He even gets going on collision detection! The work that’s being done here is pretty amazing, but it’s starting to hit a level of complexity that I personally don’t believe is a good idea to build in HTML. Fortunately, where CSS starts to get a bit stretched in its capabilities, WebGL picks up and gets going! Now that’s a technology I’m really looking forward to getting stuck into. Hopefully I’ll soon have the chance to write about that too!


One final note on this, we’ve got some vendor prefixes you might want to be aware of, this is a list of those required as of writing (the values are arbitrary, they’re just there for completeness!):

  -webkit-perspective-origin: 75% 75%;
          perspective-origin: 75% 75%;

  -webkit-perspective: 1200px;
          perspective: 1200px;

  -webkit-transform-origin: top right;
      -ms-transform-origin: top right;
          transform-origin: top right;

  -webkit-transform-style: preserve-3d;
     -moz-transform-style: preserve-3d;
      -ms-transform-style: preserve-3d;
          transform-style: preserve-3d;

  -webkit-transform: rotateY(45deg);
          transform: rotateY(45deg);
« Prev Article
Next Article »