WakaMotion
WakaMotion is an optional wakaPAC plugin that provides reactive motion sensor properties. It handles accelerometer data, tilt angles, iOS permission flow, and axis inversion calibration — all exposed as reactive properties that update your UI automatically.
Getting Started
WakaMotion is a separate file that must be loaded after wakapac.js and registered before any components are created.
<script src="https://cdn.jsdelivr.net/gh/quellabs/wakapac@main/wakapac.min.js"></script>
<script src="https://cdn.jsdelivr.net/gh/quellabs/wakapac@main/plugins/wakamotion.min.js"></script>
<script>
wakaPAC.use(wakaMotion);
wakaPAC('#app', {
// motion properties are now available
});
</script>
Once registered, all motion properties are automatically injected into every component. No additional configuration is required on non-iOS devices. On iOS 13+, the user must explicitly grant permission — see iOS Permission.
devicemotion listener as soon as it is registered. If you only need motion on specific pages, only load wakamotion.js on those pages.
Reactive Properties
All properties below are injected into every wakaPAC component automatically. They update in real-time as sensor data arrives and trigger reactive template updates just like your own data properties.
motionSupported
Type: boolean | Updates: Never | Writable: No
Whether the browser exposes the DeviceMotion API at all. false on most desktop browsers and devices without motion hardware. Always check this before showing any motion-dependent UI.
<div data-pac-bind="visible: !motionSupported">
Motion sensor not available on this device.
</div>
<div data-pac-bind="visible: motionSupported">
Tilt: {{ motionTiltX }}°
</div>
motionPermissionRequired
Type: boolean | Updates: Once, after permission is granted | Writable: No
true on iOS 13+ where motion data requires explicit user permission before becoming available. false everywhere else. Call wakaMotion.requestMotionPermission() from a user gesture (e.g. a button click) to trigger the permission prompt.
<button data-pac-bind="visible: motionPermissionRequired, click: requestMotion">
Enable Motion Sensor
</button>
motionPermissionGranted
Type: boolean | Updates: Once, after permission is granted | Writable: No
true if motion data is allowed to flow. On non-iOS devices this is true immediately — no permission is required. On iOS 13+ it starts as false and flips to true once wakaMotion.requestMotionPermission() resolves with 'granted'.
<div data-pac-bind="visible: motionPermissionGranted">
Tilt: {{ motionTiltX }}°
</div>
motionHasAcceleration
Type: boolean | null | Updates: Once, on first event | Writable: No
Whether the device provides acceleration data. null until the first devicemotion event arrives, then a stable true or false. Some Android WebViews report accelerationIncludingGravity as null on every event despite supporting the API — this flag exposes that.
motionHasRotationRate
Type: boolean | null | Updates: Once, on first event | Writable: No
Whether the device has a gyroscope. null until the first event, then stable. Many budget tablets and mid-range phones ship without a gyroscope — rotation properties will remain null on such devices. Use this to conditionally show or hide rotation-based UI.
<div data-pac-bind="visible: motionHasRotationRate">
Rotation alpha: {{ motionRotationAlpha }}°/s
</div>
<div data-pac-bind="visible: motionHasRotationRate === false">
Rotation not available on this device.
</div>
motionHasRotationRate === false is different from !motionHasRotationRate — the latter also matches null (not yet determined). Use strict equality when you want to distinguish "confirmed unavailable" from "not yet known".
motionAccelerationX / motionAccelerationY / motionAccelerationZ
Type: number | null | Updates: devicemotion events | Writable: No
Raw acceleration including gravity along each device axis, in m/s² — this is the accelerationIncludingGravity field from the DeviceMotion spec, not the gravity-subtracted acceleration field. The gravity component is what makes tilt calculation possible: when the device is stationary, these values reflect gravitational pull on each axis, which is exactly what motionTiltX and motionTiltY are derived from. Values are rounded to 2 decimal places to suppress floating point noise. null until the first event arrives, or if the device does not report acceleration data.
motionAccelerationX— left/right axis. Positive = right edge tilted down (W3C spec)motionAccelerationY— forward/back axis. Positive = bottom edge tilted downmotionAccelerationZ— through-screen axis. ~9.81 m/s² when device is flat face-up (gravity)
wakaMotion.setMotionAxisInversion() to correct known devices, or wakaMotion.detectMotionAxisInversion() to run interactive calibration.
motionTiltX / motionTiltY
Type: number | null | Updates: devicemotion events | Writable: No
Tilt angle in whole degrees from horizontal, derived from acceleration using asin(axis / g). More intuitive than raw acceleration values for building tilt-based interfaces. null until the first event.
motionTiltX— left/right roll.0°= flat,+90°= right edge fully down,-90°= left edge fully downmotionTiltY— forward/back pitch.0°= flat,+90°= bottom edge fully down,-90°= top edge fully down
Range is -90° to +90° and is stable across all device orientations.
<div id="tilt-demo">
<p>Left/right: {{ motionTiltX }}°</p>
<p>Fwd/back: {{ motionTiltY }}°</p>
</div>
wakaMotion.setMotionThreshold(0.5); // suppress noise below 0.5 m/s²
wakaPAC('#tilt-demo', {
computed: {
// Map tilt to pixel offset for a ball-in-maze effect
ballX() { return (this.motionTiltX ?? 0) * 2; },
ballY() { return (this.motionTiltY ?? 0) * 2; }
}
});
motionRotationAlpha / motionRotationBeta / motionRotationGamma
Type: number | null | Updates: devicemotion events | Writable: No
Rotation rate around each axis in degrees per second. Requires a gyroscope. Remains null on devices without gyroscope hardware. Check motionHasRotationRate before using these values.
motionRotationAlpha— rotation around the Z axis (spinning flat on a table)motionRotationBeta— rotation around the X axis (tilting forward/back)motionRotationGamma— rotation around the Y axis (tilting left/right)
motionAxisInversion
Type: object | Updates: after calibration or manual set | Writable: No
The current axis inversion multipliers as { x: 1|-1, y: 1|-1 }. A value of -1 means that axis is being corrected before values are broadcast. Updated automatically when wakaMotion.detectMotionAxisInversion() completes, or immediately when wakaMotion.setMotionAxisInversion() is called.
motionAxisDetectionStep
Type: string | Updates: during calibration | Writable: No
Current state of the axis inversion calibration flow. Possible values:
'idle'— not started'tilt-x'— waiting for the user to tilt the right edge down'tilt-y'— waiting for the user to tilt the bottom edge down'done'— both axes detected, inversion applied'timeout'— user did not tilt within the allotted time
Watch this property to drive custom calibration UI. For simple cases use motionAxisDetectionStepLabel instead.
motionAxisDetectionStepLabel
Type: string | Updates: during calibration | Writable: No
Human-readable instruction for the current calibration step. Empty string when step is 'idle'. Allows a minimal calibration UI with no mapping code required.
<div id="calibration">
<button data-pac-bind="click: calibrate">Calibrate axes</button>
<p>{{ motionAxisDetectionStepLabel }}</p>
</div>
wakaPAC('#calibration', {
calibrate() {
wakaMotion.detectMotionAxisInversion();
},
watch: {
motionAxisDetectionStep(step) {
if (step === 'done') {
// Persist the result so it can be restored on next page load
localStorage.setItem('axisInversion', JSON.stringify(this.motionAxisInversion));
}
}
}
});
iOS Permission
iOS 13+ requires an explicit user gesture before motion data is available. WakaMotion defers listener attachment on iOS — no data flows until wakaMotion.requestMotionPermission() is called from a tap handler. Calling it at page load causes a silent denial. The recommended pattern is to check motionPermissionRequired and show a button only when needed:
<div id="app">
<!-- Shown only on iOS 13+ before permission is granted -->
<button data-pac-bind="visible: motionPermissionRequired, click: requestMotion">
Enable Motion Sensor
</button>
<!-- Shown once data is flowing -->
<div data-pac-bind="visible: motionPermissionGranted">
Tilt: {{ motionTiltX }}°
</div>
</div>
wakaPAC('#app', {
async requestMotion() {
const result = await wakaMotion.requestMotionPermission();
if (result !== 'granted') {
console.warn('Motion permission denied.');
}
}
});
API
These methods configure WakaMotion behavior. They can be called at any time, though threshold and inversion settings are typically set before the first component is created.
wakaPAC.use(wakaMotion)
Registers the WakaMotion plugin with the wakaPAC runtime. Must be called before any wakaPAC() calls.
| Parameter | Type | Description |
|---|---|---|
wakaMotion | object | The WakaMotion plugin object. |
Returns void | ||
wakaMotion.setMotionThreshold(threshold)
Suppress events unless at least one axis changes by more than threshold m/s² since the last dispatched event. Prevents constant re-renders from sensor noise while the device is stationary. Default is 0 (every event is dispatched). Recommended starting value for tilt UIs: 0.5.
| Parameter | Type | Description |
|---|---|---|
threshold | number | Minimum axis change in m/s² required to dispatch an event. Default: 0. |
Returns void | ||
wakaMotion.setMotionThreshold(0.5);
wakaMotion.requestMotionPermission()
Request iOS motion sensor permission. Must be called from within a user gesture (button click). On non-iOS this is a no-op that immediately resolves with 'granted'.
| Parameter | Type | Description |
|---|---|---|
Returns Promise<string> | Resolves to 'granted', 'denied', or 'error'. | |
wakaPAC('#app', {
async requestMotion() {
const result = await wakaMotion.requestMotionPermission();
if (result !== 'granted') {
this.errorMessage = 'Motion permission denied.';
}
}
});
wakaMotion.setMotionAxisInversion(x, y)
Manually correct for devices that report an acceleration axis inverted relative to the W3C spec. To identify inversion manually: hold the device flat and tilt the right edge down — motionAccelerationX should go positive. If it goes negative, the X axis is inverted on that device.
| Parameter | Type | Description |
|---|---|---|
x | number | -1 to invert the X axis, 1 to leave it as reported. |
y | number | -1 to invert the Y axis, 1 to leave it as reported. |
Returns void | ||
wakaMotion.setMotionAxisInversion(-1, 1); // X inverted, Y normal
wakaMotion.detectMotionAxisInversion(options)
Guides the user through two tilt gestures to automatically detect and correct axis inversion. Updates motionAxisDetectionStep, motionAxisDetectionStepLabel, and motionAxisInversion as detection progresses. Safe to call from a button click handler. Ignored if calibration is already in progress.
| Parameter | Type | Description |
|---|---|---|
options.threshold | number | Minimum acceleration in m/s² to register a deliberate tilt. Default: 4. |
options.timeout | number | Milliseconds to wait per axis before setting step to 'timeout'. Default: 8000. |
Returns void | ||
wakaMotion.detectMotionAxisInversion({ threshold: 4, timeout: 10000 });
wakaMotion.getMotionCapabilities()
Returns the current motion sensor capabilities of the device. hasAcceleration and hasRotationRate are null until the first devicemotion event arrives — call this after motion data has started flowing for reliable results.
| Parameter | Type | Description |
|---|---|---|
Returns object | Plain object with motionSupported, requiresPermission, hasAcceleration, and hasRotationRate flags. | |
const caps = wakaMotion.getMotionCapabilities();
// {
// motionSupported: true,
// requiresPermission: false,
// hasAcceleration: true,
// hasRotationRate: null // not yet known
// }
Quick Reference
Reactive Properties
| Property | Type | Description |
|---|---|---|
motionSupported |
boolean | Browser exposes the DeviceMotion API |
motionPermissionRequired |
boolean | iOS 13+: explicit permission needed before data flows |
motionPermissionGranted |
boolean | Permission granted; true immediately on non-iOS, after grant on iOS |
motionHasAcceleration |
boolean | null | Device reports acceleration data (null until first event) |
motionHasRotationRate |
boolean | null | Device has a gyroscope (null until first event) |
motionAccelerationX |
number | null | Acceleration incl. gravity, left/right axis (m/s²) |
motionAccelerationY |
number | null | Acceleration incl. gravity, forward/back axis (m/s²) |
motionAccelerationZ |
number | null | Acceleration incl. gravity, through-screen axis (m/s²) |
motionTiltX |
number | null | Left/right tilt from horizontal in degrees (-90 to +90) |
motionTiltY |
number | null | Forward/back tilt from horizontal in degrees (-90 to +90) |
motionRotationAlpha |
number | null | Rotation rate, Z axis (deg/s) — gyroscope required |
motionRotationBeta |
number | null | Rotation rate, X axis (deg/s) — gyroscope required |
motionRotationGamma |
number | null | Rotation rate, Y axis (deg/s) — gyroscope required |
motionAxisInversion |
object | Current axis inversion multipliers { x: 1|-1, y: 1|-1 } |
motionAxisDetectionStep |
string | Calibration flow state (idle, tilt-x, tilt-y, done, timeout) |
motionAxisDetectionStepLabel |
string | Human-readable instruction for the current calibration step |