Sensor Apis Dom Vs Generic
The browser has two sensor APIs from different vendors. The old DOM events work on iOS. The new Generic Sensor API does not. Here is how both work and when to use each.
There are two JavaScript APIs for reading phone sensors. One was built by Apple around 2009. The other was built by Chrome in 2017. They read the same hardware, they do not replace each other, and which one works depends on which device your user has.
The iOS question determines everything. If your users are on iPhone, only the old API works. If they're on Android or desktop Chrome, either works. The sensible strategy is to prefer the new API and fall back to the old one.
The DOM Events API (The Original)
Apple created this one. Chrome adopted it. Firefox and others followed. It works everywhere -- including iOS Safari, which is the part that matters.
The pattern is a simple event listener on the window object:
window.addEventListener('devicemotion', event => {
const { x, y, z } = event.acceleration;
const withGravity = event.accelerationIncludingGravity;
const { alpha, beta, gamma } = event.rotationRate;
});
window.addEventListener('deviceorientation', event => {
const { alpha, beta, gamma } = event; // compass and tilt angles
});There's no setup, no constructor, no start() call. The event fires continuously once you add the listener. You cannot stop it and you cannot control the frequency. The sensor reports at its default rate and your handler receives every update.
This was fine when it shipped -- web developers were just happy to have the data. The limitations became more visible as use cases grew more complex.
The Generic Sensor API (The Modern One)
Chrome introduced the Generic Sensor API in 2017 as a cleaner alternative. It's promise-aware, constructor-based, and gives you control that the DOM events API never had.
const sensor = new Accelerometer({ frequency: 60 });
sensor.addEventListener('reading', () => {
const { x, y, z } = sensor;
updateUI(x, y, z);
});
sensor.addEventListener('error', event => {
console.error(event.error.name, event.error.message);
});
sensor.start(); // begin reading
// later: sensor.stop(); // stop readingThe frequency option sets how many readings per second you receive -- 60 means roughly 60 Hz, matching a typical display refresh. You can call sensor.stop() to pause and sensor.start() again to resume. This is the first time web developers could control sensor polling rate or stop it on demand.
The tradeoff: it is not available on iOS or Firefox. Safari has not implemented it. The light-green API tier applies here -- Chromium-only.
ExpandTwo sensor APIs: DOM Events (everywhere) vs Generic Sensor API (Chromium only)
Available Generic Sensor Constructors
The Generic Sensor API ships with a set of concrete sensor classes:
new Accelerometer({ frequency: 60 }) // raw force, X/Y/Z
new LinearAccelerationSensor({ frequency: 60 }) // force minus gravity
new Gyroscope({ frequency: 60 }) // rotation, alpha/beta/gamma
new Magnetometer({ frequency: 60 }) // compass direction
new AbsoluteOrientationSensor({ frequency: 60 }) // device orientation in space
new RelativeOrientationSensor({ frequency: 60 }) // orientation relative to start
new AmbientLightSensor({ frequency: 1 }) // lux -- behind a flagLinearAccelerationSensor is worth noting specifically. The DOM events API lets you request acceleration with or without gravity via event.acceleration vs event.accelerationIncludingGravity. The Generic Sensor API handles this with separate constructors.
Controlling Sensors with Permissions-Policy
Both APIs respect the Permissions-Policy HTTP header. If you want to block sensor access on your page or in embedded iframes -- which is worth doing if your page doesn't use sensors -- the header syntax looks like:
Permissions-Policy: accelerometer=(), gyroscope=(), magnetometer=()This is independent of the permission dialog question. The header controls whether your JavaScript can even attempt to read the sensor. The permission model governs the user-facing dialog -- for sensors on Android, there typically isn't one, but the Permissions-Policy layer still applies.
Which API to Use
The pragmatic approach for cross-browser sensor code:
function startSensorReading(onReading) {
if ('Accelerometer' in window) {
// Generic Sensor API -- Android Chrome, Edge
const sensor = new Accelerometer({ frequency: 60 });
sensor.addEventListener('reading', () => onReading(sensor.x, sensor.y, sensor.z));
sensor.start();
} else if ('DeviceMotionEvent' in window) {
// DOM Events fallback -- iOS Safari, Firefox
window.addEventListener('devicemotion', event => {
const { x, y, z } = event.acceleration ?? event.accelerationIncludingGravity;
onReading(x, y, z);
});
}
}Check for the Generic Sensor constructor first. Fall back to the DOM event. This gets you coverage across Android Chrome, iOS Safari, and Firefox.
One wrinkle remains: on iOS 13 and later, the DOM events fallback requires explicit permission before it works. That permission model -- and the story of how it broke thousands of web experiences overnight -- is what the next post covers.
The Essentials
- The DOM Events API (
devicemotion,deviceorientation) works on iOS, Android, and Firefox. It always streams, has nostop(), and no frequency control. - The Generic Sensor API uses constructors per sensor type. It supports frequency control,
start()/stop(), and is Chromium-only -- not available on iOS or Firefox. - Check for
'Accelerometer' in windowfirst, fall back to'DeviceMotionEvent' in windowto cover both platforms in a single code path. - Use
Permissions-Policy: accelerometer=()in HTTP headers to block sensor access on pages that don't need it.
Further Reading and Watching
- Generic Sensors API (YouTube -- Code & Supply) -- talk covering the Generic Sensor API specification and how it unifies the sensor surface
- Generic Sensor API -- web.dev -- full reference with code examples for each sensor constructor and the polyfill strategy for cross-browser coverage
Keep reading