Using Device Motion on the Web

This post is more than 2 years old.

I'm currently working on an article for TDN that looks at how web standards have advanced in comparison to the default list of plugins supported for Apache Cordova. In my research, I looked at the Device Orientation API. Specifically, I was interested in device motion. For Cordova, motion and orientation are split into two plugins, but spec-wise, they are covered in - well - one spec. In general, it is a fairly simply API to use. Here is an example from the Mozilla Developer Network page on device motion:

window.addEventListener('devicemotion', function(event) {
  console.log(event.acceleration.x + ' m/s2');
});

Fairly simple, right? Unlike the Cordova plugin which lets you get the current value or listen for the values at an interval, the web standards API is just an event. When it fires, it fires.

I did some basic testing where I just listened for the event and logged the values. I found that in many cases, it fired all the time, whether or not the device was moving. I added a bit of logic to my code to limit my logging to cases where X, Y, or Z had at least a value of 1 or higher (after taking the absolute value). This restored some sanity at least. The best news, though, is how well this is supported. From CanIUse:

CanIUse

That's dang good! About the only issue here is desktop Safari, which we all know is only used to watch Apple keynotes.

Ok, so with that being done, I thought I'd take a stab at building "shake" support. Basically - monitor device motion and detect when it has been 'shaken' - which really comes down to math. Given we know how much they have moved, have they moved "enough" to consider it a shake.

About a year ago, I built a demo of this for Ionic: Working with Ionic Native - Shake, Rattle, and Roll. The logic boils down to tracking the deltas (changes) in motion over the 3 axis and keeping track of when they move a significant amount a few times. Some of the numbers I used came from me simply playing with hardware and seeing what "felt" right, so obviously it could be adjusted. Here's what I came up with:

document.addEventListener('DOMContentLoaded', init, false);
function init() {
	console.log('Engage');
	window.addEventListener('devicemotion', motion, false);
}

let lastX, lastY, lastZ;
let moveCounter = 0;

function motion(e) {
	let acc = e.acceleration;
	if(!acc.hasOwnProperty('x')) {
		acc = e.accelerationIncludingGravity;
	}

	if(!acc.x) return;

	//only log if x,y,z > 1
	if(Math.abs(acc.x) >= 1 &&
	Math.abs(acc.y) >= 1 &&
	Math.abs(acc.z) >=1) {
		//console.log('motion', acc);
		if(!lastX) {
			lastX = acc.x;
			lastY = acc.y;
			lastZ = acc.z;
			return;
		}

		let deltaX = Math.abs(acc.x - lastX);
		let deltaY = Math.abs(acc.y - lastY);
		let deltaZ = Math.abs(acc.z - lastZ);
		
		if(deltaX + deltaY + deltaZ > 3) {
			moveCounter++;
		} else {
			moveCounter = Math.max(0, --moveCounter);
		}

		if(moveCounter > 2) {
			console.log('SHAKE!!!');
			moveCounter = 0;
		}

		lastX = acc.x;
		lastY = acc.y;
		lastZ = acc.z;
		
	}
}

Looking at the motion event, one of the first things I had to do was account for acceleration being null and switching to accelerationIncludingGravity. If you read the spec, you'll see this little nugget:

Implementations that are unable to provide acceleration data without the effect of gravity (due, for example, to the lack of a gyroscope) may instead supply the acceleration including the effect of gravity. This is less useful in many applications but is provided as a means of providing best-effort support. In this case, the accelerationIncludingGravity attribute must be initialized with the acceleration of the hosting device, plus an acceleration equal and opposite to the acceleration due to gravity.

Basically - it's a fallback. Once I've figured out where to find my values, I begin doing some parsing.

The first IF block was used to control logging initially - I was trying to avoid very small movements. In theory I don't need it since I'm more concerned about the deltas, but I kept it there as another way to limit false positives.

The rest of the method looks at the deltas, see if they are "enough" (and again, this was arbitrary based on my testing) and when I feel like it's time to consider it a shake, I log it.

I tested this with my iPhone and it worked really well. I tested this on my desktop Chrome, and it worked in terms of having an event to listen to, but obviously I didn't shake my desktop. I tested this on my Surface Book with Chrome and MS Edge and both worked great.

Here is a stupid animated GIF where I ran it in MS Edge and actually shook my laptop:

Alt text

Pro Tip: Don't do that.

So at this point, I could actually call some other method to actually do something on the shake event, but I'll leave that up to the reader. What's cool is - you could do something like, "Shake to reload", and as long as you provide some other way to reload, this is totally fine than in cases where it isn't supported.

You can find the code for this here: https://github.com/cfjedimaster/webdemos/tree/master/device_motion

You can run this as well: https://cfjedimaster.github.io/webdemos/device_motion/

p.s. I probably don't need the DOMContentLoaded event listener at all. When I first started, I thought I'd do a complete demo and maybe show random content on shake, but I figured I'd be lazy and leave it as is. :)

Raymond Camden's Picture

About Raymond Camden

Raymond is a developer advocate for HERE Technologies. He focuses on JavaScript, serverless and enterprise cat demos. If you like this article, please consider visiting my Amazon Wishlist or donating via PayPal to show your support. You can even buy me a coffee!

Lafayette, LA https://www.raymondcamden.com

Archived Comments

Comment 1 by Šime Vidas posted on 4/26/2017 at 2:25 AM

I tried the demo on my Moto G4 and got reminded that shaking the phone toggles the flashlight, by default :) Consider logging into the page instead of the console, so that folks can easily check if it works on their phones.

Comment 2 (In reply to #1) by Raymond Camden posted on 4/26/2017 at 11:28 AM

Heh - that's actually kind of a cool feature.

Comment 3 by Gary F posted on 4/26/2017 at 9:11 PM

Presumably the web page must be in the foreground for the event to trigger? I know this is only an example, but a shake is really useful when you can't easily access a button. e.g. a panic alarm, a trigger to send a pre-written message, etc. In such cases the browser is unlikely to be active and the phone probably on standby. Or have browsers now got the ability to detect events while in a background or standby state? Just wondering.

Comment 4 (In reply to #3) by Raymond Camden posted on 4/26/2017 at 9:14 PM

I'm honestly not sure. Obviously on desktop the events would still fire. I'd need to connect my device and check. One moment while I try.

Comment 5 (In reply to #4) by Raymond Camden posted on 4/26/2017 at 9:22 PM

In Android, as I expected, when the web page wasn't focused, it does not report. I think this is fair for a web thing though.

Comment 6 (In reply to #5) by Gary F posted on 4/27/2017 at 12:04 AM

Thanks for confirming that, Ray. Hmm, what if motion is used in a Cordova app. Same result because it's a browser wrapper?. No need to check, I will get on your nerves! I'm assuming it won't work either.

Comment 7 (In reply to #6) by Raymond Camden posted on 4/27/2017 at 2:38 PM

There is a background process plugin for Cordova that _may_ allow you to notice this, and then you can respond to it. Maybe.

Comment 8 (In reply to #7) by Raymond Camden posted on 4/27/2017 at 2:39 PM

But obviously, before you even go that route, test to see if the event fires in a Cordova app in the background. I don't think it will - but test.

Comment 9 by Stephen Cunliffe posted on 5/4/2017 at 12:40 PM

Is this line a typo? "lastY = acc.z;" inside your motion function? e.g. shouldn't it be lastZ?

Comment 10 (In reply to #9) by Raymond Camden posted on 5/4/2017 at 2:00 PM

Yep, thanks, fixed here and in source. (The fix here will take about five minutes to show up.)

Comment 11 by awebdeveloper posted on 5/5/2017 at 10:21 AM

did you write that article for TDN

Comment 12 (In reply to #11) by Raymond Camden posted on 5/5/2017 at 11:41 AM

Yes - it should be live Monday.

Comment 13 (In reply to #12) by awebdeveloper posted on 5/5/2017 at 1:59 PM

Can't wait to read it