useHooks.iov5.0.0
DocsBlogGitHub
Hooks
No hooks found in any category.

useDeviceOrientation

sensors

Installation

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

NameTypeDefaultDescription
options?UseDeviceOrientationOptions-Configuration options for device orientation

Parameter Properties

options properties:

NameTypeDescription
absolute?booleanWhether to request absolute orientation values (auto-starts listening)

Return Type

UseDeviceOrientationReturn
PropertyTypeDescription
orientationDeviceOrientation | nullCurrent device orientation data including alpha, beta, gamma values
isSupportedbooleanWhether DeviceOrientationEvent is supported
errorstring | nullError message if orientation access failed
requestPermission() => Promise<boolean>Request permission for device orientation (required on iOS 13+)
startListening() => voidStart listening to orientation changes
stopListening() => voidStop listening to orientation changes
isListeningbooleanWhether 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
Buy Me A Coffee