Hooks
No hooks found in any category.
useDeviceOrientation
sensorsInstallation
npx usehooks-cli@latest add use-device-orientationDescription
A hook for accessing device orientation data using the DeviceOrientationEvent API. Provides alpha, beta, and gamma rotation values along with compass heading on supported devices.
Parameters
| Name | Type | Default | Description |
|---|---|---|---|
| options? | UseDeviceOrientationOptions | - | Configuration options for device orientation |
Parameter Properties
options properties:
| Name | Type | Description |
|---|---|---|
| absolute? | boolean | Whether to request absolute orientation values (auto-starts listening) |
Return Type
UseDeviceOrientationReturn
| Property | Type | Description |
|---|---|---|
| orientation | DeviceOrientation | null | Current device orientation data including alpha, beta, gamma values |
| isSupported | boolean | Whether DeviceOrientationEvent is supported |
| error | string | null | Error message if orientation access failed |
| requestPermission | () => Promise<boolean> | Request permission for device orientation (required on iOS 13+) |
| startListening | () => void | Start listening to orientation changes |
| stopListening | () => void | Stop listening to orientation changes |
| isListening | boolean | Whether currently listening to orientation changes |
Examples
Basic Orientation Display
Display device orientation values with manual start/stop
1import { useDeviceOrientation } from '@usehooks/use-device-orientation';
2
3function OrientationDisplay() {
4 const {
5 orientation,
6 isSupported,
7 isListening,
8 error,
9 requestPermission,
10 startListening,
11 stopListening
12 } = useDeviceOrientation();
13
14 const handleStart = async () => {
15 const hasPermission = await requestPermission();
16 if (hasPermission) {
17 startListening();
18 }
19 };
20
21 if (!isSupported) {
22 return <div>Device orientation not supported</div>;
23 }
24
25 return (
26 <div>
27 <div>
28 <button onClick={handleStart} disabled={isListening}>
29 Start Listening
30 </button>
31 <button onClick={stopListening} disabled={!isListening}>
32 Stop Listening
33 </button>
34 </div>
35
36 <p>Status: {isListening ? 'Listening' : 'Stopped'}</p>
37
38 {error && <p>Error: {error}</p>}
39
40 {orientation && (
41 <div>
42 <h3>Device Orientation:</h3>
43 <p>Alpha (Z-axis): {orientation.alpha?.toFixed(2)}°</p>
44 <p>Beta (X-axis): {orientation.beta?.toFixed(2)}°</p>
45 <p>Gamma (Y-axis): {orientation.gamma?.toFixed(2)}°</p>
46 <p>Absolute: {orientation.absolute ? 'Yes' : 'No'}</p>
47 {orientation.webkitCompassHeading && (
48 <p>Compass: {orientation.webkitCompassHeading.toFixed(2)}°</p>
49 )}
50 </div>
51 )}
52 </div>
53 );
54}Compass Application
Create a simple compass using device orientation
1import { useDeviceOrientation } from '@usehooks/use-device-orientation';
2import { useEffect, useState } from 'react';
3
4function Compass() {
5 const { orientation, requestPermission, startListening } = useDeviceOrientation();
6 const [compassHeading, setCompassHeading] = useState(0);
7
8 useEffect(() => {
9 const initCompass = async () => {
10 const hasPermission = await requestPermission();
11 if (hasPermission) {
12 startListening();
13 }
14 };
15 initCompass();
16 }, []);
17
18 useEffect(() => {
19 if (orientation) {
20 // Use webkitCompassHeading if available, otherwise use alpha
21 const heading = orientation.webkitCompassHeading ?? orientation.alpha ?? 0;
22 setCompassHeading(heading);
23 }
24 }, [orientation]);
25
26 const compassStyle = {
27 width: '200px',
28 height: '200px',
29 border: '2px solid #333',
30 borderRadius: '50%',
31 position: 'relative' as const,
32 margin: '20px auto',
33 backgroundColor: '#f0f0f0'
34 };
35
36 const needleStyle = {
37 position: 'absolute' as const,
38 top: '10px',
39 left: '50%',
40 width: '2px',
41 height: '80px',
42 backgroundColor: 'red',
43 transformOrigin: 'bottom center',
44 transform: `translateX(-50%) rotate(${compassHeading}deg)`,
45 transition: 'transform 0.3s ease'
46 };
47
48 return (
49 <div style={{ textAlign: 'center' }}>
50 <h2>Compass</h2>
51 <div style={compassStyle}>
52 <div style={needleStyle}></div>
53 <div style={{ position: 'absolute', top: '5px', left: '50%', transform: 'translateX(-50%)' }}>N</div>
54 <div style={{ position: 'absolute', bottom: '5px', left: '50%', transform: 'translateX(-50%)' }}>S</div>
55 <div style={{ position: 'absolute', left: '5px', top: '50%', transform: 'translateY(-50%)' }}>W</div>
56 <div style={{ position: 'absolute', right: '5px', top: '50%', transform: 'translateY(-50%)' }}>E</div>
57 </div>
58 <p>Heading: {compassHeading.toFixed(1)}°</p>
59 </div>
60 );
61}Auto-start with Absolute Values
Automatically start listening with absolute orientation values
1import { useDeviceOrientation } from '@usehooks/use-device-orientation';
2
3function AbsoluteOrientation() {
4 const {
5 orientation,
6 isSupported,
7 isListening,
8 error
9 } = useDeviceOrientation({ absolute: true });
10
11 if (!isSupported) {
12 return <div>Device orientation not supported</div>;
13 }
14
15 return (
16 <div>
17 <h3>Absolute Device Orientation</h3>
18 <p>Status: {isListening ? 'Active' : 'Inactive'}</p>
19
20 {error && (
21 <div style={{ color: 'red' }}>
22 <p>Error: {error}</p>
23 <p>Note: On iOS, you may need to enable motion access in Settings.</p>
24 </div>
25 )}
26
27 {orientation && (
28 <div style={{ fontFamily: 'monospace' }}>
29 <div>Alpha (Z): {orientation.alpha?.toFixed(3)}°</div>
30 <div>Beta (X): {orientation.beta?.toFixed(3)}°</div>
31 <div>Gamma (Y): {orientation.gamma?.toFixed(3)}°</div>
32 <div>Absolute: {orientation.absolute ? '✓' : '✗'}</div>
33
34 {/* Visual representation */}
35 <div style={{ marginTop: '20px' }}>
36 <div>Tilt Forward/Back: {orientation.beta ? (orientation.beta > 0 ? 'Forward' : 'Back') : 'Level'}</div>
37 <div>Tilt Left/Right: {orientation.gamma ? (orientation.gamma > 0 ? 'Right' : 'Left') : 'Level'}</div>
38 </div>
39 </div>
40 )}
41 </div>
42 );
43}Dependencies
react
Notes
- •Requires user permission on iOS 13+ devices
- •HTTPS required in production environments
- •Alpha represents rotation around Z-axis (0-360°)
- •Beta represents rotation around X-axis (-180° to 180°)
- •Gamma represents rotation around Y-axis (-90° to 90°)
- •WebKit compass heading available on some iOS devices
- •Automatically cleans up event listeners on unmount
Implementation
1'use client';
2
3import { useState, useEffect, useCallback } from "react";
4
5interface DeviceOrientation {
6 alpha: number | null;
7 beta: number | null;
8 gamma: number | null;
9 absolute: boolean;
10 webkitCompassHeading?: number;
11 webkitCompassAccuracy?: number;
12}
13
14interface UseDeviceOrientationOptions {
15 absolute?: boolean;
16}
17
18interface UseDeviceOrientationReturn {
19 orientation: DeviceOrientation | null;
20 isSupported: boolean;
21 error: string | null;
22 requestPermission: () => Promise<boolean>;
23 startListening: () => void;
24 stopListening: () => void;
25 isListening: boolean;
26}
27
28// Extend DeviceOrientationEvent interface for webkit properties
29declare global {
30 interface DeviceOrientationEvent {
31 webkitCompassHeading?: number;
32 webkitCompassAccuracy?: number;
33 }
34}
35
36// Check if DeviceOrientationEvent is supported
37export const isDeviceOrientationSupported =
38 typeof globalThis !== 'undefined' && 'DeviceOrientationEvent' in globalThis;
39
40// Backwards-compatible alias used internally (keeps existing name)
41const isSupported = isDeviceOrientationSupported;
42
43export const useDeviceOrientation = (
44 options: UseDeviceOrientationOptions = {}
45): UseDeviceOrientationReturn => {
46 const [orientation, setOrientation] = useState<DeviceOrientation | null>(
47 null
48 );
49 const [isListening, setIsListening] = useState(false);
50 const [error, setError] = useState<string | null>(null);
51
52 // Handle orientation change
53 const handleOrientationChange = useCallback(
54 (event: DeviceOrientationEvent) => {
55 setOrientation({
56 alpha: event.alpha,
57 beta: event.beta,
58 gamma: event.gamma,
59 absolute: event.absolute,
60 webkitCompassHeading: event.webkitCompassHeading,
61 webkitCompassAccuracy: event.webkitCompassAccuracy,
62 });
63 },
64 []
65 );
66
67 // Request permission for iOS 13+ devices
68 const requestPermission = useCallback(async (): Promise<boolean> => {
69 if (!isSupported) {
70 setError("DeviceOrientationEvent is not supported");
71 return false;
72 }
73
74 try {
75 setError(null);
76
77 // Check if permission is required (iOS 13+)
78 if (
79 typeof (DeviceOrientationEvent as any).requestPermission === "function"
80 ) {
81 const permission = await (
82 DeviceOrientationEvent as any
83 ).requestPermission();
84
85 if (permission === "granted") {
86 return true;
87 } else {
88 setError("Permission denied for device orientation");
89 return false;
90 }
91 }
92
93 // Permission not required or already granted
94 return true;
95 } catch (err) {
96 const errorMessage =
97 err instanceof Error ? err.message : "Failed to request permission";
98 setError(errorMessage);
99 return false;
100 }
101 }, []);
102
103 // Start listening to orientation changes
104 const startListening = useCallback(() => {
105 if (!isSupported || isListening) return;
106
107 try {
108 setError(null);
109 window.addEventListener(
110 "deviceorientation",
111 handleOrientationChange,
112 true
113 );
114 setIsListening(true);
115 } catch (err) {
116 const errorMessage =
117 err instanceof Error ? err.message : "Failed to start listening";
118 setError(errorMessage);
119 }
120 }, [isListening, handleOrientationChange]);
121
122 // Stop listening to orientation changes
123 const stopListening = useCallback(() => {
124 if (!isSupported || !isListening) return;
125
126 try {
127 window.removeEventListener(
128 "deviceorientation",
129 handleOrientationChange,
130 true
131 );
132 setIsListening(false);
133 setOrientation(null);
134 } catch (err) {
135 const errorMessage =
136 err instanceof Error ? err.message : "Failed to stop listening";
137 setError(errorMessage);
138 }
139 }, [isListening, handleOrientationChange]);
140
141 // Auto-start listening if absolute option is provided
142 useEffect(() => {
143 if (options.absolute !== undefined && isSupported) {
144 const initializeOrientation = async () => {
145 const hasPermission = await requestPermission();
146 if (hasPermission) {
147 startListening();
148 }
149 };
150
151 initializeOrientation();
152 }
153
154 return () => {
155 if (isListening) {
156 stopListening();
157 }
158 };
159 }, [options.absolute, stopListening, isListening, requestPermission, startListening]);
160
161 // Cleanup on unmount
162 useEffect(() => {
163 return () => {
164 if (isListening) {
165 stopListening();
166 }
167 };
168 }, [isListening, stopListening]);
169
170 return {
171 orientation,
172 isSupported,
173 error,
174 requestPermission,
175 startListening,
176 stopListening,
177 isListening,
178 };
179};
180