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.
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.
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.
| Methods | Type | Description |
|---|---|---|
getAvailability() => MotionAvailabilityWhich sensors this device can stream. | () => MotionAvailability | Which 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) => () => void | Stream a sensor. Interval 0 to 1000. Returns a remover. |
| Availability | Type | Description |
|---|---|---|
accelerometerbooleanWhether acceleration streams. | boolean | Whether acceleration streams. |
gyroscopebooleanWhether rotation rate streams. | boolean | Whether rotation rate streams. |
magnetometerbooleanWhether magnetic field streams. | boolean | Whether magnetic field streams. |
deviceMotionbooleanWhether fused motion streams. | boolean | Whether fused motion streams. |
| Reading | Type | Description |
|---|---|---|
sensorMotionSensorWhich sensor produced this reading. | MotionSensor | Which sensor produced this reading. |
timestampMsnumberUnix time of the reading. | number | Unix time of the reading. |
valueMotionVectorThe x, y, z measurement. | MotionVector | The x, y, z measurement. |
gravityMotionVectorGravity vector. Device motion only. | MotionVector | Gravity vector. Device motion only. |
userAccelerationMotionVectorGravity-free acceleration. Device motion only. | MotionVector | Gravity-free acceleration. Device motion only. |
rotationRateMotionVectorRotation rate. Device motion only. | MotionVector | Rotation rate. Device motion only. |
attitudeMotionVectorRoll, pitch, yaw. Device motion only. | MotionVector | Roll, pitch, yaw. Device motion only. |
| Vector | Type | Description |
|---|---|---|
xnumberRight in device coordinates. | number | Right in device coordinates. |
ynumberUp in device coordinates. | number | Up in device coordinates. |
znumberOut of the screen. | number | Out of the screen. |
| iOS | Android | Web | |
|---|---|---|---|
| Engine | Core Motion | Sensors | Web events |
| Thread | Own queue | Worker | Main |
| Fastest | 100 Hz | Sensor max | Varies |
| Magnet | Yes | Yes | No |
| Permission | None | Manifest | Prompt |
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.
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.