Hooks
No hooks found in any category.
useDeviceOrientation
sensorsInstallation
npx usehooks-cli@latest add use-device-orientation
Description
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
36export const useDeviceOrientation = (
37 options: UseDeviceOrientationOptions = {}
38): UseDeviceOrientationReturn => {
39 const [orientation, setOrientation] = useState<DeviceOrientation | null>(
40 null
41 );
42 const [isListening, setIsListening] = useState(false);
43 const [error, setError] = useState<string | null>(null);
44
45 // Check if DeviceOrientationEvent is supported
46 const isSupported =
47 typeof window !== "undefined" && "DeviceOrientationEvent" in window;
48
49 // Handle orientation change
50 const handleOrientationChange = useCallback(
51 (event: DeviceOrientationEvent) => {
52 setOrientation({
53 alpha: event.alpha,
54 beta: event.beta,
55 gamma: event.gamma,
56 absolute: event.absolute,
57 webkitCompassHeading: event.webkitCompassHeading,
58 webkitCompassAccuracy: event.webkitCompassAccuracy,
59 });
60 },
61 []
62 );
63
64 // Request permission for iOS 13+ devices
65 const requestPermission = useCallback(async (): Promise<boolean> => {
66 if (!isSupported) {
67 setError("DeviceOrientationEvent is not supported");
68 return false;
69 }
70
71 try {
72 setError(null);
73
74 // Check if permission is required (iOS 13+)
75 if (
76 typeof (DeviceOrientationEvent as any).requestPermission === "function"
77 ) {
78 const permission = await (
79 DeviceOrientationEvent as any
80 ).requestPermission();
81
82 if (permission === "granted") {
83 return true;
84 } else {
85 setError("Permission denied for device orientation");
86 return false;
87 }
88 }
89
90 // Permission not required or already granted
91 return true;
92 } catch (err) {
93 const errorMessage =
94 err instanceof Error ? err.message : "Failed to request permission";
95 setError(errorMessage);
96 return false;
97 }
98 }, [isSupported]);
99
100 // Start listening to orientation changes
101 const startListening = useCallback(() => {
102 if (!isSupported || isListening) return;
103
104 try {
105 setError(null);
106 window.addEventListener(
107 "deviceorientation",
108 handleOrientationChange,
109 true
110 );
111 setIsListening(true);
112 } catch (err) {
113 const errorMessage =
114 err instanceof Error ? err.message : "Failed to start listening";
115 setError(errorMessage);
116 }
117 }, [isSupported, isListening, handleOrientationChange]);
118
119 // Stop listening to orientation changes
120 const stopListening = useCallback(() => {
121 if (!isSupported || !isListening) return;
122
123 try {
124 window.removeEventListener(
125 "deviceorientation",
126 handleOrientationChange,
127 true
128 );
129 setIsListening(false);
130 setOrientation(null);
131 } catch (err) {
132 const errorMessage =
133 err instanceof Error ? err.message : "Failed to stop listening";
134 setError(errorMessage);
135 }
136 }, [isSupported, isListening, handleOrientationChange]);
137
138 // Auto-start listening if absolute option is provided
139 useEffect(() => {
140 if (options.absolute !== undefined && isSupported) {
141 const initializeOrientation = async () => {
142 const hasPermission = await requestPermission();
143 if (hasPermission) {
144 startListening();
145 }
146 };
147
148 initializeOrientation();
149 }
150
151 return () => {
152 if (isListening) {
153 stopListening();
154 }
155 };
156 }, [options.absolute, isSupported]);
157
158 // Cleanup on unmount
159 useEffect(() => {
160 return () => {
161 if (isListening) {
162 stopListening();
163 }
164 };
165 }, [isListening, stopListening]);
166
167 return {
168 orientation,
169 isSupported,
170 error,
171 requestPermission,
172 startListening,
173 stopListening,
174 isListening,
175 };
176};
177