UNPKG

expo-haptics

Version:

Provides access to the system's haptics engine on iOS, vibration effects on Android, and Web Vibration API on web.

92 lines (82 loc) 2.98 kB
import { NotificationFeedbackType, ImpactFeedbackStyle } from './Haptics.types'; /** * The vibrate pattern holds an array of values describes alternating periods in which the device is * vibrating and not vibrating. Each value in the array is converted to an integer, then interpreted * alternately as the duration of milliseconds the device should and should not vibrate. */ const vibrationPatterns: Record< NotificationFeedbackType | ImpactFeedbackStyle | 'selection', VibratePattern > = { [NotificationFeedbackType.Success]: [40, 100, 40], [NotificationFeedbackType.Warning]: [50, 100, 50], [NotificationFeedbackType.Error]: [60, 100, 60, 100, 60], [ImpactFeedbackStyle.Light]: [40], [ImpactFeedbackStyle.Medium]: [50], [ImpactFeedbackStyle.Heavy]: [60], [ImpactFeedbackStyle.Soft]: [35], [ImpactFeedbackStyle.Rigid]: [45], selection: [50], }; function isVibrationAvailable(): boolean { return typeof window !== 'undefined' && 'navigator' in window && 'vibrate' in navigator; } /** * On iOS Safari, `navigator.vibrate` is not supported. Instead, we can trigger * haptic feedback by creating a hidden `<input type="checkbox" switch>` element * and programmatically clicking it. This exploits the native haptic feedback that * iOS provides for switch toggle interactions. * * @see https://github.com/tijnjh/ios-haptics */ function iOSSwitchHaptic(): void { try { const labelEl = document.createElement('label'); labelEl.ariaHidden = 'true'; labelEl.style.display = 'none'; const inputEl = document.createElement('input'); inputEl.type = 'checkbox'; inputEl.setAttribute('switch', ''); labelEl.appendChild(inputEl); document.head.appendChild(labelEl); labelEl.click(); document.head.removeChild(labelEl); } catch {} } /** * Whether the device likely supports touch haptics (coarse pointer = touchscreen). */ const supportsCoarsePointer = typeof window !== 'undefined' && window.matchMedia('(pointer: coarse)').matches; function hapticWithVibrateFallback(pattern: VibratePattern): void { if (isVibrationAvailable()) { navigator.vibrate(pattern); return; } if (supportsCoarsePointer) { iOSSwitchHaptic(); } } export default { async notificationAsync(type: NotificationFeedbackType): Promise<void> { if (isVibrationAvailable()) { navigator.vibrate(vibrationPatterns[type]); return; } if (!supportsCoarsePointer) return; // Map notification types to repeated haptic pulses const count = type === NotificationFeedbackType.Error ? 3 : 2; for (let i = 0; i < count; i++) { if (i > 0) { await new Promise((resolve) => setTimeout(resolve, 120)); } iOSSwitchHaptic(); } }, async impactAsync(style: ImpactFeedbackStyle): Promise<void> { hapticWithVibrateFallback(vibrationPatterns[style]); }, async selectionAsync(): Promise<void> { hapticWithVibrateFallback(vibrationPatterns.selection); }, };