So, CSS3 3d transforms are cool - when they work as expected that is. Recently I was trying to achieve a more complex 3d transform than a single axis rotation. I am sure we are all used to things like rotateX(xDeg), rotateY(yDeg), and rotateZ(zDeg) which seem to work as expected in all browsers that support it. But what if you want to rotate an element about its diagonal? Things start to break up across browsers when using the above properties and consistency is lost in interpretation... How do we solve this? Read on to find out how deep the rabbit hole goes...

You may also know of a property called rotate3d that allows you to define the axis of rotation. For example I have the following code:

  transform: translate(-30px, -30px) rotate3d(1, -1, 0, 180deg) rotateZ(-90deg);

What I wanted to do here was translate the element -30px in x and -30px in y, followed by a rotation across the diagonal of the element (top right to bottom left) by 180 degrees and then rotate around the z axis by -90 degrees (as a rectangle would end up rotated 90 degrees when rotated about its diagonal). This worked perfectly in Chrome as you can see from the first GIF below on the left. Firefox however had other ideas. In the second GIF on the right you can see that the rotation does not occur in the same way and even leads to a weird "snapping" bug for the text once rotated.

Chrome works as expected rotating about the diagonal
Firefox on the other hand rotates in a different manner - look closely and introduces a weird text snapping effect at the end of the transform.

It occured to me that there is yet another way to perform 3D transformations - matrix3d - pure maths fun. In this case you can use a matrix to define the transforms you wish to apply. So the below code is equivalent to the line of code above:

  transform:matrix3d(-0.0000000000000002220446049250313, -0.9999999999999998, 0.00000000000000008659560562354932, 0, -0.9999999999999998, -0.0000000000000002220446049250313, 0.00000000000000008659560562354932, 0, -0.00000000000000008659560562354932, -0.00000000000000008659560562354932, -1, 0, -30, -30, 0, 1) matrix(0.00000000000000006123233995736766, -1, 1, 0.00000000000000006123233995736766, 0, 0);

Woah! WTF I hear you all cry. Well this was my reaction as well. However I found a way to easily calculate what values you need to use here via a nice little function exposed in chrome - getComputedStyle()

So using my original line of code, I can clearly combine the translation and the rotation about the diagonal into 1 operation. So I changed the CSS to be:

  transform: translate(-30px, -30px) rotate3d(1, - 1, 0, 180deg);
  /* Notice I dropped the final 90 degree rotation. */

Opening Chrome to ensure this achieved its desired effect I could then use the Chrome dev tools console to type:

  var elem = document.getElementById('wantedElementId');
  var style = getComputedStyle(elem);
  style.webkitTransform;

What this snippet does is to print out Chrome's representation of the computed style for that element. When we echo style.webkitTransform to the console it shows us that internally Chrome actually converts our CSS to a matrix3d transform! Huzzah I don't have to calculate all those numbers myself! I can now take this code and replace my CSS with that which then works as expected in all browsers.

I simply then repeated this for the extra 90 degree rotation at the end which resolved to a matrix transformation (2d) so added this after the matrix 3d transform in the CSS and all was good (yep if you look at the above matrix code there are 2 transforms going on - matrix3d() and a regular 2d matrix())

I hope this little write up saves you time if you end up doing some more advanced 3D transforms too and run into similar issues :-) If you have any questions, comments, you can reach me over on Google+ or Twitter where I post lots of stuff like this!


3,437 7 22