DeviceMotionEvent and DeviceOrientationEvent. These high-frequency readings
are permission-gated, nullable, and time-sensitive. Feature-detect the APIs,
request access in response to a clear user action, and keep the app usable when
access is denied or readings stop.requestPermission() on both event constructors. They share
one sensor permission, so call either available method once:let sensorListenersAttached = false;
function attachSensorListeners() {
if (sensorListenersAttached) {
return;
}
window.addEventListener('devicemotion', handleMotion);
window.addEventListener('deviceorientation', handleOrientation);
sensorListenersAttached = true;
}
async function requestSensorAccess() {
const orientation = window.DeviceOrientationEvent;
const motion = window.DeviceMotionEvent;
if (!orientation && !motion) {
showSensorUnavailable('Sensors are not supported.');
return;
}
const permissionOwner =
typeof orientation?.requestPermission === 'function'
? orientation
: typeof motion?.requestPermission === 'function'
? motion
: null;
try {
const state = permissionOwner
? await permissionOwner.requestPermission()
: 'granted';
if (state !== 'granted') {
showSensorUnavailable('Sensor access was not granted.');
return;
}
attachSensorListeners();
} catch (error) {
showSensorUnavailable('Sensor access is unavailable.');
}
}
document
.getElementById('enable-sensors')
.addEventListener('click', requestSensorAccess);
function stopSensors() {
window.removeEventListener('devicemotion', handleMotion);
window.removeEventListener('deviceorientation', handleOrientation);
sensorListenersAttached = false;
}
DeviceMotionEvent can provide acceleration, acceleration including gravity,
rotation rate, and the interval between readings. Guard both each containing
object and each numeric member:function finite(value) {
return typeof value === 'number' && Number.isFinite(value);
}
function handleMotion(event) {
const acceleration = event.accelerationIncludingGravity;
if (
acceleration &&
finite(acceleration.x) &&
finite(acceleration.y) &&
finite(acceleration.z)
) {
updateAcceleration({
x: acceleration.x,
y: acceleration.y,
z: acceleration.z,
interval: event.interval,
});
}
const rotation = event.rotationRate;
if (
rotation &&
finite(rotation.alpha) &&
finite(rotation.beta) &&
finite(rotation.gamma)
) {
updateRotation({
alpha: rotation.alpha,
beta: rotation.beta,
gamma: rotation.gamma,
});
}
}
| Field | Unit | Availability |
|---|---|---|
accelerationIncludingGravity.x/y/z | Meters per second squared | Values can be null; validate before use. |
acceleration.x/y/z | Meters per second squared | Gravity-removed readings can be unavailable. |
rotationRate.alpha/beta/gamma | Degrees per second | Values can be null. This differs from Generic Sensor Gyroscope, which uses radians per second. |
interval | Milliseconds | Treat as reported timing, not a guaranteed fixed frequency. |
alpha, beta, gamma, and absolute. Values can be
missing or relative to different reference frames, so inspect absolute and
validate every number before use:function handleOrientation(event) {
if (
!finite(event.alpha) ||
!finite(event.beta) ||
!finite(event.gamma)
) {
showOrientationUnavailable();
return;
}
updateOrientation({
alpha: event.alpha,
beta: event.beta,
gamma: event.gamma,
absolute: event.absolute === true,
receivedAt: performance.now(),
});
}
deviceorientationabsolute can be available on supported
builds; feature-detect it rather than assuming universal browser support.requestPermission() call resumes forwarding without
requiring the app to register them again.navigator.geolocation. One-shot positions are
normally sourced through the paired phone because the glasses do not contain a
standalone satellite-navigation receiver. Availability and accuracy depend on
permission, the paired-device connection, environment, and location services.function requestPosition() {
if (!navigator.geolocation) {
showLocationUnavailable('Location is not supported.');
return;
}
navigator.geolocation.getCurrentPosition(
(position) => {
const {coords} = position;
showPosition({
latitude: coords.latitude,
longitude: coords.longitude,
accuracy: coords.accuracy,
altitude: coords.altitude,
altitudeAccuracy: coords.altitudeAccuracy,
heading: coords.heading,
speed: coords.speed,
timestamp: position.timestamp,
});
},
(error) => {
showLocationUnavailable(locationErrorMessage(error));
},
{timeout: 15000},
);
}
| Error | Meaning |
|---|---|
PERMISSION_DENIED | The user denied location access. |
POSITION_UNAVAILABLE | The runtime could not obtain a position. This can have several causes and does not prove that the phone is offline. |
TIMEOUT | A position was not available within the effective timeout. |
coords.accuracy rather than promising a fixed accuracy range. Altitude,
altitude accuracy, heading, and speed can be null.watchPosition() when a feature needs ongoing updates, and clear the watch
as soon as it is no longer needed:let watchId = null;
function startLocationWatch() {
watchId = navigator.geolocation.watchPosition(
(position) => updateMap(position.coords),
(error) => showLocationUnavailable(locationErrorMessage(error)),
{enableHighAccuracy: true},
);
}
function stopLocationWatch() {
if (watchId !== null) {
navigator.geolocation.clearWatch(watchId);
watchId = null;
}
}
| Method | Supported options |
|---|---|
getCurrentPosition() | timeout. The host applies its own default and upper bound. enableHighAccuracy and maximumAge are not used for one-shot requests. |
watchPosition() | enableHighAccuracy is a request for the best available watch source and can fall back. timeout and maximumAge are not used for watches. |