navigation

Using CSS 3D transforms can be a bit challenging. It’s new, support is sketchy, and it requires a type of spatial thinking that we aren’t used to on the web (yet). But the results are worth it. With a bit of CSS and some javascript for event binding, you can turn a boring old table into a 3D histogram like this one:

30

20

60

80

10

40

20

50

30

Step 1 - Markup

First, we need the HTML. We’ll create two container elements that are quite common in 3D css work:

a viewport: acts as the projection layer or “camera”

a world: which holds every other child element which we can easily rotate

I also like to add another element, the ground, which is just a plane that objects will sit on.

Inside this tree of three elements, we’ll add our table, which contains the 3D histogram values. Here’s the markup:

Step 2 - The environment CSS

Next we need to set up our 3D environment. For the sake of simplicity, I will omit -vendor-prefixed values.

First, the viewport. I some relative positioning, and dimensions, but the important values are the perspective and perspective-origin values. Roughly speaking, the perspective controls how far the “camera” is from the origin, and perspective-origin defines its position.

Next, the world. Again, we set some positioning and dimension values, but the important values for the world are tranform and transform-style. The transform value will rotate the world to its starting orientation. The transform-style is set to preserve-3d. This is so that all children of the world will also have 3D appearances. If we didn’t set this, all children of the world element would be smushed into the world plane with no depth. Note: we’re also setting preserve-3d on every child of the world too. Firefox doesn’t seem to properly propagate this setting down the DOM tree… so this gets around that.

The tricky part is creating the bars. What we need to do is use some javascript to replace the numeric values in the table with some markup to act as the 3D bar. The markup we want to insert into every table cell looks like this:

The .bar element just contains the five faces (we don’t need a bottom face). To do the replacement, it’s easiest to use a template. We can store this markup in a script tag with a type="text/template" or similar. The browser won’t try to run this as javascript, and we can simply use .innerHTML to get the content. The javascript will look like this (using jQuery):

// get the template
var tpl = $('#bartpl').html();
// insert template markup into each td
// and set the font size to be the value of the td
$('.histogram-3d td').each(function(){
var val = this.innerHTML;
$(this)
.html(tpl)
.css('font-size', val+'px')
;
});

Pretty straightforward, but why are we setting a font size!? This is the magic bullet to control the size of the 3D bars.

We’re going to use dimensions relative to the font size (em), in order to control the height of the bars. So if we set a font-size on the bar element, the faces will resize appropriately to the correct height. Snazzy, eh? I can’t take full credit for this strategy, though.

So, let’s add the bar css. First, we simply resize the bar container to fill the table cell, and set a relative position. Then for every face element, set a background, an absolute position, relative size, and orientation in 3D space. The differences in color correspond to different shadings of each face. If you want to get fancy, you can use the Photon CSS lighting engine, but beware, I found some buggy behaviour in firefox when using it table elements. More about that later. Here’s the rest of the CSS.

Step 4 - Make it rotate!

The last step can be done in many different ways. We need to attach mouse/touch events to change the world element’s orientation in order to rotate the histogram.

Generally this means tracking the mouse/touch event coordinates and mapping them to an angle. This is how I’ve done it, but you can probably come up with a cleaner and more general way to do this, right? :)

Caveats

Unfortunately, things get messier because of cross-browser support. I’m not even talking about IE. I’m talking about Firefox. Turns out 3D transforms won’t work with CSS table layouts in Firefox. This means, for Firefox (and perhaps other browsers), we need to reset the layouts of the table, tr, and td elements to use only block and inline-block display values… which is annoying. What I’ve done on this page is browser sniff for firefox, and add a body class no-3dtablelayout. Then I override the layouts within that scope like so: