HTML5: Device Orientation (accelerometer) events

Like most newer HTML5 web APIs, the device orientation events represent a basic idea: if mobile devices have the ability to expose accelerometer data to native applications, why not use that in HTML5 projects too? Why not let web developers use the three-dimensional orientation of a device as just another input? That way, they can detect things like the device itself tilting or turning away from the user and respond accordingly.

As one of the few resources on the API, the Mozilla Developers Network page explains the three different primary values representing the twisting (alpha), tipping (beta), and tilting (gamma) device orientation values in the following way:

The DeviceOrientationEvent.alpha value represents the motion of the device around the z axis, represented in degrees with values ranging from 0 to 360.

The DeviceOrientationEvent.beta value represents the motion of the device around the x axis, represented in degrees with values ranging from -180 to 180.
This represents a front to back motion of the device.

The DeviceOrientationEvent.gamma value represents the motion of the device around the y axis, represented in degrees with values ranging from -90 to 90.
This represents a left to right motion of the device.

The current Editor’s Draft of the W3C specification lists three events: devicemotion, deviceorientation, and compassneedscalibration. As of this writing, most (~60%) of browsers support the first two, motion and orientation, with the last event having partial support everywhere. The reasoning being that many vendors either do not currently have code to call calibration events or may not even have the ability to call such an event at all depending on the operating system and its permissions.

Testing for support is very straightforward, with most implementations of a wrapper, including my own, using the common double-negation approach for testing for properties of the window object. If it has a ‘DeviceMotionEvent’ property (is not ‘undefined’), we register a listener to record all changes to the motion values. The same approach is taken with ‘DeviceOrientationEvent,’ testing for its existence and then registering a listening function too.

var Accelerometer = function() {
...
if (!!window.DeviceMotionEvent) {
window.addEventListener("devicemotion", handleMotion, true);
self.supported = true;
}
if (!!window.DeviceOrientationEvent) {
window.addEventListener("deviceorientation", handleOrientation, true);
self.supported = true;
}
window.addEventListener("compassneedscalibration", function(event) {
self.supported = true;
alert('Your compass needs calibrating!');
event.preventDefault();
}, true);
...
}

Logging the data is even easier. We overwrite the local object values with whatever was passed in for the event.

function handleOrientation(event) {
absolute = event.absolute;
alpha = event.alpha;
beta = event.beta;
gamma = event.gamma;
}
function handleMotion(event) {
acceleration = accelerationIncludingGravity = rotationRate = {};
acceleration.x = event.acceleration.x;
acceleration.y = event.acceleration.y;
acceleration.z = event.acceleration.z;
accelerationIncludingGravity.x = event.accelerationIncludingGravity.x;
accelerationIncludingGravity.y = event.accelerationIncludingGravity.y;
accelerationIncludingGravity.z = event.accelerationIncludingGravity.z;
rotationRate.alpha = event.rotationRate.alpha;
rotationRate.beta = event.rotationRate.beta;
rotationRate.gamma = event.rotationRate.gamma;
interval = event.interval;
}

Exposing the data is just as simple, with basic functions connected to the self (object’s ‘this’) scope and doing some basic testing if the initial (null) value of each variable has been overwritten at any time. If it was, either a device motion or orientation event was triggered at some point and its values are returned. Otherwise, no support was found and a 0 is returned instead.

self.getAlpha = function() {
return (alpha !== null) ? alpha : 0;
};
self.getBeta = function() {
return (beta !== null) ? beta : 0;
};
self.getGamma = function() {
return (gamma !== null) ? gamma : 0;
};
self.getAcceleration = function() {
return (acceleration !== null) ? acceleration : 0;
};
self.getAccelerationIncludingGravity = function() {
return (accelerationIncludingGravity !== null) ? accelerationIncludingGravity : 0;
};
self.getRotationRate = function() {
return (rotationRate !== null) ? rotationRate : 0;
};
self.getInterval = function() {
return (interval !== null) ? interval : 0;
};

My complete code here shows the small addition of the variable names I am using, matching the event names themselves, as well as the recorded support copied from the Can I Use page and the explaination of the DeviceOrientaitonEvent values shown above.

/**
* An accelerometer object for detecting device orientation
* and motion (if supported)
*
* Chrome 7+, Firefox 6+, IE11+, iOS Safari 4.0+, Android Browser 3.0, Blackberry 10.0
* http://caniuse.com/#feat=deviceorientation
*
* The DeviceOrientationEvent.alpha value represents the motion of the device around the z axis,
* represented in degrees with values ranging from 0 to 360.
*
* The DeviceOrientationEvent.beta value represents the motion of the device around the x axis,
* represented in degrees with values ranging from -180 to 180.
* This represents a front to back motion of the device.
*
* The DeviceOrientationEvent.gamma value represents the motion of the device around the y axis,
* represented in degrees with values ranging from -90 to 90.
* This represents a left to right motion of the device.
*
* @property {boolean} supported If orientation events are supported in the current context
*/
var Accelerometer = function() {
var self = this;
self.supported = false;
var absolute = null,
alpha = null,
beta = null,
gamma = null,
acceleration = null,
accelerationIncludingGravity = null,
rotationRate = null,
interval = null;
self.getAlpha = function() {
return (alpha !== null) ? alpha : 0;
};
self.getBeta = function() {
return (beta !== null) ? beta : 0;
};
self.getGamma = function() {
return (gamma !== null) ? gamma : 0;
};
self.getAcceleration = function() {
return (acceleration !== null) ? acceleration : 0;
};
self.getAccelerationIncludingGravity = function() {
return (accelerationIncludingGravity !== null) ? accelerationIncludingGravity : 0;
};
self.getRotationRate = function() {
return (rotationRate !== null) ? rotationRate : 0;
};
self.getInterval = function() {
return (interval !== null) ? interval : 0;
};
if (!!window.DeviceMotionEvent) {
window.addEventListener("devicemotion", handleMotion, true);
self.supported = true;
}
if (!!window.DeviceOrientationEvent) {
window.addEventListener("deviceorientation", handleOrientation, true);
self.supported = true;
}
window.addEventListener("compassneedscalibration", function(event) {
self.supported = true;
alert('Your compass needs calibrating!');
event.preventDefault();
}, true);
function handleOrientation(event) {
absolute = event.absolute;
alpha = event.alpha;
beta = event.beta;
gamma = event.gamma;
}
function handleMotion(event) {
acceleration = accelerationIncludingGravity = rotationRate = {};
acceleration.x = event.acceleration.x;
acceleration.y = event.acceleration.y;
acceleration.z = event.acceleration.z;
accelerationIncludingGravity.x = event.accelerationIncludingGravity.x;
accelerationIncludingGravity.y = event.accelerationIncludingGravity.y;
accelerationIncludingGravity.z = event.accelerationIncludingGravity.z;
rotationRate.alpha = event.rotationRate.alpha;
rotationRate.beta = event.rotationRate.beta;
rotationRate.gamma = event.rotationRate.gamma;
interval = event.interval;
}
};