JavaScript Device Orientation Table
| Piece | What it does | Field note |
|---|---|---|
deviceorientation event | The pose stream | fires up to 60 times a second with alpha, beta and gamma in degrees - mobile-first, HTTPS-only, and silent on sensor-less desktops |
e.beta / e.gamma | The tilt pair | beta is front-back tilt around the x axis (flat 0, upright 90); gamma is left-right roll from -90 to 90 - the level-app axes |
e.alpha | The compass angle | rotation around the z axis - but in the default frame 0 is wherever the device faced when the page loaded, not north |
e.absolute | The true-north flag | true means alpha is Earth-referenced; without it, compass duty falls to webkitCompassHeading or manual screen-orientation math |
webkitCompassHeading | The Safari truth | non-standard but real: heading clockwise from true north - the pragmatic compass answer on iOS, absent elsewhere |
DeviceOrientationEvent.requestPermission() | The iOS gate | iOS 13+: call from a real tap, promise resolves granted or denied; outside a gesture it rejects - one grant covers devicemotion too |
DeviceMotionEvent | The dynamics sibling | acceleration, accelerationIncludingGravity and rotationRate on the devicemotion listener - shake, flip and impact detection live here |
the device-frame trap | The classic bug | all angles are device-frame values - a landscape-locked page reading them raw is measuring a world it rotated itself |
The deviceorientation event is the phone telling your page how it is physically tilted: three angles - alpha (compass rotation around the z axis), beta (front-to-back tilt), gamma (left-right tilt) - delivered as an event stream while the device moves. One addEventListener('deviceorientation', fn) and your page can build a level, a steering control, a star map or a measuring tool with zero native code. The event is high-frequency (typically 60 times a second), HTTPS-only, and mobile-first: desktops without sensors simply never fire it, which makes feature detection and a mouse fallback part of the design rather than an afterthought.
Bottom line: three angles, one honest caveat. Alpha is the trickiest - in the default (non-absolute) frame it is relative to wherever the device was when the listener started, not to true north; Safari exposes the real compass heading as a non-standard webkitCompassHeading property, while cross-browser true-north work needs the absolute event or manual math that also accounts for screen orientation. The iOS wrinkle is famous: since iOS 13, DeviceOrientationEvent.requestPermission() must be called from a real user tap before any events flow - Android needs no such prompt, so the same code path branches by platform. The sibling DeviceMotionEvent covers the linear-acceleration and rotation-rate half of the story, and both families share the same quiet privacy settlement: sensors only work in the foreground, in a secure context, behind a gesture-gated door where the platform decided it needed one.
The integration reality: this is sensor plumbing, and sensor plumbing lives or dies on calibration and noise discipline. Raw values jitter - a level app that draws beta directly looks drunk, so the standard move is a low-pass filter (newAngle = 0.9 * oldAngle + 0.1 * incoming) or requiring N stable readings before acting. Values drift after temperature changes or rough handling; magnetometer-based compass readings lie near metal and magnets; and the axes themselves are expressed in device coordinates, which means a landscape-locked page reading portrait values is a classic bug. Treat the API as a physical instrument with a warm-up, not a boolean - sample, filter, sanity-check, and only then drive UI.
How to use
- Feature-detect and branch by platform: if (!('DeviceOrientationEvent' in window)) show a mouse fallback; on iOS also check typeof DeviceOrientationEvent.requestPermission === 'function' to know the permission gate exists.
- Open the iOS gate from a real tap: btn.onclick = () => { DeviceOrientationEvent.requestPermission().then(state => { if (state === 'granted') start(); }).catch(() => showFallback()); } - calling it outside a gesture rejects, and one grant lasts for the session, not forever.
- Listen and read the three angles: window.addEventListener('deviceorientation', e => { tilt(e.beta, e.gamma); compass(e.alpha, e.webkitCompassHeading); }); - all three arrive on every event, already in degrees.
- Filter before you draw: keep a smoothed value per axis - sm = sm === null ? e.beta : sm * 0.9 + e.beta * 0.1 - and render the smoothed one; the raw stream is for math, not for pixels.
- Clean up like any high-frequency listener: window.removeEventListener('deviceorientation', handler) when the tool loses focus or the page hides - 60 events a second is battery burn if nobody is watching the result.
Frequently asked questions
What do alpha, beta and gamma actually measure - and why does every compass app seem to get alpha wrong?
They are Tait-Bryan angles of the device relative to a coordinate frame: beta is rotation around the x axis (the phone's left-right edge) - flat on a table is 0, tilted toward you grows positive, standing upright is 90; gamma is rotation around the y axis (top-bottom edge) - flat is 0, rolled onto its left side approaches 90; alpha is rotation around the z axis (pointing out of the screen) and is the compass-flavored one: 0 when the top of the phone points toward the reference direction. The compass trouble is the reference frame. In the default non-absolute mode, alpha 0 is wherever the device was oriented when the page loaded - perfectly fine for relative controls (steering, tilting), useless for navigation. For true north you need either the absolute orientation event (alpha measured against Earth's coordinate frame, where supported) or Safari's non-standard webkitCompassHeading, which directly reports the compass heading in degrees clockwise from north. And the second trap: all three angles are in device coordinates, so a landscape device reports beta/gamma as if you had rotated the world - production code combines the angles with the current screen orientation angle before trusting any of them. The recipe: relative controls use raw alpha/beta/gamma; anything that claims a compass direction uses webkitCompassHeading on Safari or the absolute event elsewhere, with a screen-orientation correction and a visible calibration state.
How does the iOS 13+ requestPermission gate actually behave, and what is the right UX around it?
It is a function, not an event: DeviceOrientationEvent.requestPermission() returns a promise resolving to 'granted' or 'denied', and it must be invoked from a user gesture - a tap handler on a button you showed for exactly this purpose. Call it from script context (a load handler, a timer) and it rejects; that rejection is not a bug you can route around, it is the platform enforcing visible consent for motion sensors, motivated by the 2019 wave of motion-sensor fingerprinting and advertising abuse. The UX that works: an explicit 'enable motion' button (or gate the motion feature behind the first natural tap), with the Android path silently skipping the gate - feature-detect the function itself (typeof DeviceOrientationEvent.requestPermission === 'function') to know which world you are in. Handle all three outcomes of the promise: granted means attach listeners and go; denied means the user said no at the system prompt - respect it, show the manual fallback, and do not re-prompt on every interaction (iOS will keep denying and your button looks broken); rejected promise means the gesture-context violation or a system-level restriction - the same fallback UI, plus a console note for debugging. One grant covers both deviceorientation and devicemotion for the session; it does not persist across reloads, so a motion-heavy page should remember the choice in its own UI state and re-gate quietly on next visit rather than nagging.
Why do my readings jitter, drift, or lie near magnets - and what is the standard counter-move?
Three separate phenomena with three separate counters. Jitter is sensor noise: MEMS gyros and accelerometers produce values that wobble by a degree or two sample to sample, so direct rendering shivers - the standard counter is a low-pass filter (keep 90 percent of the previous smoothed value, blend in 10 percent of the new sample) or requiring a stable window (N consecutive readings within a threshold) before acting; for games, raw values feel responsive, for instruments, filtered values feel precise. Drift is thermal and mechanical: sensors recalibrate as the device heats up or after being dropped, so a level that read true at minute one may sit a degree off at minute twenty - counter it with a visible re-zero control (let the user place the phone on a known-flat surface and capture the current values as the new baseline). Compass error is environmental: the magnetometer that backs alpha-in-absolute-mode and webkitCompassHeading deflects near steel frames, car bodies, magnetic mounts and even some laptop lids - the counter is honesty in the UI: show a calibration hint when readings disagree across device movement (the classic figure-eight wave is the platform convention for forcing magnetometer recalibration), and never present a compass heading to more precision than the hardware deserves (whole degrees, no decimals). The meta-rule: treat every reading as an estimate with a confidence, and design the interface so users can see when the instrument is unsure.
Where does DeviceMotionEvent fit, and why do motion and orientation events live behind the same gates?
DeviceMotionEvent is the acceleration half of the same physical story: its event object carries acceleration (m/s squared in the three device axes, optionally excluding gravity via accelerationIncludingGravity), rotationRate (deg/s of alpha/beta/gamma-style rotation), and interval (the nominal sample period in ms). Orientation tells you pose - which way the phone is facing; motion tells you dynamics - is it accelerating, shaking, falling, being shaken like a maraca. The classic motion use cases are gesture detection (shake to undo, flip to mute), step counting, and impact or fall detection; the classic orientation use cases are levels, compasses, and tilt controls. They are separate listeners (devicemotion vs deviceorientation) but share the platform's privacy settlement: on iOS both sit behind the same requestPermission() call and one grant covers both; both are secure-context-only; both stop flowing when the tab is backgrounded; and both are high-frequency enough that battery-conscious engineering applies - remove listeners when the tool is not on screen, prefer setInterval throttling of your own handler work over raw event frequency, and never leave a motion listener attached on a page the user has merely paused. The deprecation wind blowing through generic sensor APIs is worth knowing: the W3C's newer Generic Sensor API (Accelerometer, Gyroscope classes) is the long-term direction, but cross-browser reality still runs on these events - so build on devicemotion/deviceorientation today, isolate the sensor access in one small module, and the migration cost stays contained when the platform eventually moves.