Pull up any browser-based compass tool on your phone and it works instantly — no app install, no native code. That's not a trick; it's a real browser API doing real sensor fusion under the hood. Here's what's actually happening between "grant motion permission" and a needle pointing north.
The API: DeviceOrientationEvent
Browsers expose device orientation through the DeviceOrientationEvent, which fires continuously as the device moves and gives you three angles:
window.addEventListener('deviceorientation', (event) => {
console.log(event.alpha); // compass heading, 0-360°
console.log(event.beta); // front-to-back tilt, -180 to 180°
console.log(event.gamma); // left-to-right tilt, -90 to 90°
});
For a compass specifically, alpha is the value that matters — it represents rotation around the z-axis, i.e., which way the device is facing relative to north. beta and gamma describe tilt, which is useful for things like AR overlays but isn't the heading itself.
Where the numbers actually come from: sensor fusion
The browser doesn't read alpha directly off a single chip. It's computed from multiple hardware sensors combined:
Magnetometer — measures the ambient magnetic field in three axes, the same underlying sensor a standalone compass app uses
Accelerometer — measures gravity/tilt, used to figure out which way is "down" so the heading can be corrected for the phone not being held perfectly flat
Gyroscope — measures rotational velocity, used to smooth out jittery orientation changes between magnetometer readings
The OS-level sensor fusion algorithm (not something you write yourself) blends these three inputs together to produce a single, relatively stable heading value. This is why a compass built purely on deviceorientation tends to be smoother and more usable than one built by reading a raw magnetometer value alone — you're getting the benefit of tilt-compensation for free.
Absolute vs. relative orientation — the detail that trips people up
There are actually two related events, and mixing them up is one of the most common bugs in browser-compass implementations:
deviceorientation — heading may be relative to the device's initial orientation when the page loaded, not true compass north, depending on browser/platform
deviceorientationabsolute — explicitly ties the heading to Earth's magnetic north, which is what you actually want for a compass
On some platforms (notably iOS Safari), you instead get a non-standard property, event.webkitCompassHeading, which already gives you a proper 0–360° compass heading directly, and conveniently, direction is inverted relative to alpha (it increases clockwise, matching real-world compass bearings) — so implementations typically branch on feature detection:
function getHeading(event) {
if (event.webkitCompassHeading !== undefined) {
// iOS Safari: already a true compass heading
return event.webkitCompassHeading;
} else if (event.absolute && event.alpha !== null) {
// Standard absolute orientation
return 360 - event.alpha;
}
return null; // orientation not available/reliable
}
That 360 - event.alpha flip exists because alpha increases counter-clockwise in the spec, while compass bearings conventionally increase clockwise (N=0°, E=90°, S=180°, W=270°). It's a small detail, but it's exactly the kind of thing that makes a "compass" point the wrong way if you skip it.
The permission wall (and why it exists)
Since iOS 13, Safari requires an explicit, user-initiated permission request before orientation data is exposed at all — you can't request it on page load, only in direct response to a user gesture like a tap:
async function requestCompassPermission() {
if (typeof DeviceOrientationEvent.requestPermission === 'function') {
// iOS 13+ requires this explicit request, triggered by a user gesture
const permission = await DeviceOrientationEvent.requestPermission();
if (permission === 'granted') {
window.addEventListener('deviceorientationabsolute', handleOrientation);
}
} else {
// Other browsers: no explicit permission step needed
window.addEventListener('deviceorientationabsolute', handleOrientation);
}
}
button.addEventListener('click', requestCompassPermission);
This exists for a legitimate reason: orientation and motion data can be used for fingerprinting and, combined with other signals, for inferring things like typed input on nearby devices. The permission prompt is Apple's mitigation, and any browser-based compass has to design its UX around a real "tap to enable" step rather than assuming instant access.
HTTPS is non-negotiable
Motion and orientation sensors are gated behind a secure context requirement — they simply won't fire at all on a plain http:// page (localhost is exempted for development). This is standard practice for any sensor-adjacent Web API (camera, microphone, geolocation follow the same rule), so if you're testing this locally and getting null values, check your protocol before you start debugging the sensor logic itself.
Why accuracy still varies
Even with all of this correctly wired up, browser-based compass readings can wobble near metal objects, other electronics, or magnetic phone mounts — because the underlying magnetometer reading is still susceptible to local magnetic interference, exactly like a physical compass needle would be. Most implementations handle this with a simple moving average over the last several readings to smooth out jitter, rather than trying to correct interference at the software level, which is a much harder problem.
Seeing it working
If you want to see a working implementation rather than just the snippets above, online-compass.com is a browser-based compass built entirely on this API — no native app, just deviceorientationabsolute/webkitCompassHeading feature-detection and the permission flow described above, running directly in the page.
online compass
The takeaway
A browser compass isn't reading a single "direction" sensor — it's consuming an OS-level sensor-fusion output (magnetometer + accelerometer + gyroscope), gated behind a secure-context and user-permission requirement, with just enough platform inconsistency (alpha vs. webkitCompassHeading, relative vs. absolute) to make correct implementation more fiddly than it first appears. Once you know where the numbers come from and which event to actually listen for, it's a surprisingly small amount of code for something that feels like magic on first use.
Top comments (0)