One Logo Pool Ball
wwwwwwwwwwwwwwwwwww

Motion Sensors

Subscribe to accelerometer, gyroscope, magnetometer, and device motion

One.Motion reports which motion sensors the device can use and streams live readings from each. Reach for it for tilt controls, shake detection, step-adjacent activity UI, and orientation-aware layouts. For counted steps, see the pedometer; for activity classification, the OS services outside this API.

On iOS readings come from CMMotionManager in Core Motion, delivered on a dedicated operation queue. On Android they come from SensorManager, delivered on a worker thread. On web DeviceMotionEvent supplies acceleration and rotation rate, and DeviceOrientationEvent supplies attitude.

Usage

import { useEffect, useState } from 'react'
import { Text } from 'react-native'
import { One } from 'one'
function Tilt() {
const [tilt, setTilt] = useState(0)
useEffect(() => {
if (!One.Motion.getAvailability().accelerometer) return
return One.Motion.addListener(
'accelerometer',
100,
(reading) => setTilt(reading.value.x),
(code, message) => console.error(code, message)
)
}, [])
return <Text>Tilt: {tilt.toFixed(2)} g</Text>
}

Availability is a synchronous check, so the effect bails before subscribing on hardware without the sensor. The listener’s remover doubles as the effect cleanup.

How it works

getAvailability returns four booleans: accelerometer, gyroscope, magnetometer, and fused device motion. Check it first, since tablets and simulators can lack sensors; the iOS simulator has no physical motion input at all. addListener takes the sensor, an interval from 0 to 1000 milliseconds, a reading callback, and an error callback, and returns an idempotent remover. Zero requests the fastest hardware rate; a positive interval requests that delay, clamped to the hardware minimum. Core Motion clamps to its limit, commonly about 100 Hz, while Android uses the fastest constant for zero and each sensor’s minimum delay above it. The OS controls the actual frequency, so callbacks can run slower than requested. Multiple listeners share one hardware stream, each capped at its own requested rate. Basic streams need no motion permission; the Android manifest carries the normal high-sampling permission so zero can exceed 200 Hz where hardware allows.

Every reading carries a Unix timestamp, the sensor name, and an x, y, z value in device coordinates: x right, y up, z out of the screen. Accelerometer values use units of gravity, gyroscope radians per second, and magnetometer microteslas. For device motion the value is gravity-free user acceleration, plus gravity, user acceleration, rotation rate, and attitude vectors, with attitude x, y, z as roll, pitch, and yaw in radians. Android values are converted to Core Motion’s signs and units, and Android device motion fuses linear acceleration, gravity, gyroscope, and rotation vector sensors. Attitude uses an arbitrary horizontal reference on both platforms; yaw is not a compass heading.

Errors reach the error callback: the sensor is absent, the interval is invalid, or the stream failed. The wrapper throws a TypeError for a bad sensor or callback and a RangeError for a bad interval before anything subscribes.

On web each subscription keeps its own interval and remover, and device-motion fields are omitted when the browser does not provide them. Magnetometer readings are unavailable since these events expose no magnetic vectors. Availability starts false and turns true only after finite readings arrive; call getAvailability to start observing without prompting, and call addListener from a user gesture where the browser gates motion behind permission, with a refusal arriving as E_MOTION_PERMISSION. Browsers control the rate and can suspend events in hidden pages. Server rendering reports every sensor false and keeps inert listeners.

API reference

MethodsTypeDescription
getAvailability() => MotionAvailabilityWhich sensors this device can stream.
() => MotionAvailabilityWhich sensors this device can stream.
addListener(sensor: MotionSensor, intervalMs: number, onReading: (reading: MotionReading) => void, onError: (code: string, message: string) => void) => () => voidStream a sensor. Interval 0 to 1000. Returns a remover.
(sensor: MotionSensor, intervalMs: number, onReading: (reading: MotionReading) => void, onError: (code: string, message: string) => void) => () => voidStream a sensor. Interval 0 to 1000. Returns a remover.
AvailabilityTypeDescription
accelerometerbooleanWhether acceleration streams.
booleanWhether acceleration streams.
gyroscopebooleanWhether rotation rate streams.
booleanWhether rotation rate streams.
magnetometerbooleanWhether magnetic field streams.
booleanWhether magnetic field streams.
deviceMotionbooleanWhether fused motion streams.
booleanWhether fused motion streams.
ReadingTypeDescription
sensorMotionSensorWhich sensor produced this reading.
MotionSensorWhich sensor produced this reading.
timestampMsnumberUnix time of the reading.
numberUnix time of the reading.
valueMotionVectorThe x, y, z measurement.
MotionVectorThe x, y, z measurement.
gravityMotionVectorGravity vector. Device motion only.
MotionVectorGravity vector. Device motion only.
userAccelerationMotionVectorGravity-free acceleration. Device motion only.
MotionVectorGravity-free acceleration. Device motion only.
rotationRateMotionVectorRotation rate. Device motion only.
MotionVectorRotation rate. Device motion only.
attitudeMotionVectorRoll, pitch, yaw. Device motion only.
MotionVectorRoll, pitch, yaw. Device motion only.
VectorTypeDescription
xnumberRight in device coordinates.
numberRight in device coordinates.
ynumberUp in device coordinates.
numberUp in device coordinates.
znumberOut of the screen.
numberOut of the screen.

Platform differences

iOSAndroidWeb
EngineCore MotionSensorsWeb events
ThreadOwn queueWorkerMain
Fastest100 HzSensor maxVaries
MagnetYesYesNo
PermissionNoneManifestPrompt

Limits and gotchas

Examples

Detect a shake

import { useEffect } from 'react'
import { One } from 'one'
function useShake(onShake: () => void, threshold = 2.5) {
useEffect(() => {
if (!One.Motion.getAvailability().accelerometer) return
let last = 0
return One.Motion.addListener(
'accelerometer',
50,
(reading) => {
const { x, y, z } = reading.value
const magnitude = Math.sqrt(x * x + y * y + z * z)
const now = Date.now()
if (magnitude > threshold && now - last > 1000) {
last = now
onShake()
}
},
() => {}
)
}, [onShake, threshold])
}

Total acceleration magnitude spikes on a shake regardless of direction. The one-second cooldown keeps one shake from firing twice.

Show a live compass-free level

import { useEffect, useState } from 'react'
import { Text } from 'react-native'
import { One } from 'one'
function Level() {
const [pitch, setPitch] = useState(0)
useEffect(() => {
if (!One.Motion.getAvailability().deviceMotion) return
return One.Motion.addListener(
'deviceMotion',
100,
(reading) => {
if (reading.attitude) setPitch(reading.attitude.y)
},
() => {}
)
}, [])
return <Text>Pitch: {(pitch * 57.3).toFixed(1)} degrees</Text>
}

Device motion fuses the sensors into a stable attitude, so the level reads smoothly without manual filtering. The guard keeps the screen quiet where fused motion is unavailable.

Edit this page on GitHub.