Using device orientation with 3D transforms

This article provides tips on how to use device orientation information in tandem with CSS 3D transforms.

Using orientation to rotate an element

The easiest way to convert orientation data to a 3D transform is basically to use the alpha, gamma, and beta values as rotateZ, rotateX and rotateY values. There are however two corrections that should be applied to those values:

  1. The initial alpha value is 180 (device flat on the back, top of the screen pointing 12:00), so the rotateZ value should be alpha - 180
  2. The Y axis of the screen coordinate system is inverted, such that translateY(100px) moves an element 100px down, so the rotateY value should be -gamma

Finally, the order of the three different rotations is very important to accurately convert an orientation to a 3D rotation: rotateZ, then rotateX and then rotateY. Here's a simple code snippet to sum it up:

var elem = document.getElementById("view3d");

window.addEventListener("deviceorientation", function(e) {
  // remember to use vendor-prefixed transform property
  elem.style.transform =
    "rotateZ(" + ( e.alpha - 180 ) + "deg) " +
    "rotateX(" + e.beta + "deg) " +
    "rotateY(" + ( -e.gamma ) + "deg)";
});

Orientation compensation

Compensating the orientation of the device can be useful to create parallax effects or augmented reality. This is achieved by inverting the previous order of rotations and negating the alpha value:

var elem = document.getElementById("view3d");

window.addEventListener("deviceorientation", function(e) {
  // again, use vendor-prefixed transform property
  elem.style.transform =
    "rotateY(" + ( -e.gamma ) + "deg)" +
    "rotateX(" + e.beta + "deg) " +
    "rotateZ(" + - ( e.alpha - 180 ) + "deg) ";
});

rotate3d to orientation

Should you ever need to convert a rotate3d axis-angle to orientation Euler angles, you can use the following algorithm:

// convert a rotate3d axis-angle to deviceorientation angles
function orient( aa ) {
    var x = aa.x, y = aa.y, z = aa.z, a = aa.a,
        c = Math.cos( aa.a ),
        s = Math.sin( aa.a ),
        t = 1 - c,
        // axis-angle to rotation matrix
        rm00 =    c + x*x * t,
        rm10 =  z*s + y*x * t,
        rm20 = -y*s + z*x * t,
        rm01 = -z*s + x*y * t,
        rm11 =    c + y*y * t,
        rm21 =  x*s + z*y * t,
        rm02 =  y*s + x*z * t,
        rm12 = -x*s + y*z * t,
        rm22 =    c + z*z * t,

        TO_DEG = 180 / Math.PI,
        ea = [],
        n = Math.sqrt( rm22 * rm22 + rm20 * rm20 );

    // rotation matrix to Euler angles
    ea[1] = Math.atan2( -rm21, n );

    if ( n > 0.001 ) {
        ea[0] = Math.atan2( rm01, rm11 );
        ea[2] = Math.atan2( rm20, rm22 );

    } else {
        ea[0] = 0;
        ea[2] = ( rm21 > 0 ? 1 : -1 ) * Math.atan2( -rm10, rm00 );
    }

    return {
        alpha: -ea[0] * TO_DEG - 180,
        beta:  -ea[1] * TO_DEG,
        gamma:  ea[2] * TO_DEG
    };
}

See also