Expo Haptics 触觉反馈模块实战指南:iOS 触觉引擎、Android 振动与 Web Vibration 的统一封装
【免费下载链接】expoAn open-source framework for making universal native apps with React. Expo runs on Android, iOS, and the web.项目地址: https://gitcode.com/GitHub_Trending/ex/expo
expo-haptics 是 Expo 官方提供的触觉反馈模块,用于在 React Native 应用中触发系统级物理反馈:iOS 使用系统触觉引擎(Haptics Engine),Android 使用振动效果,Web 端使用 Web Vibration API。本指南将围绕该模块的安装配置、完整 API 用法与各平台底层实现原理展开,帮助你在一行代码内为用户提供高质量的触感交互体验,并理解原生层(Swift / Kotlin)与 Web 层的具体工作方式。
一、模块概览:一套 API,三种平台实现
从 packages/expo-haptics/package.json 的定义可以看出,本模块的核心定位是:"Provides access to the system's haptics engine on iOS, vibration effects on Android, and Web Vibration API on web",即:
- iOS:调用系统触觉引擎(
UINotificationFeedbackGenerator、UIImpactFeedbackGenerator、UISelectionFeedbackGenerator),提供细腻、真实的触感; - Android:通过系统
Vibrator产生振动波形,或直接调用View.performHapticFeedback触发系统预置触觉反馈; - Web:基于 Web Vibration API(
navigator.vibrate),并在不支持的 iOS Safari 上采用隐藏 switch 元素触发原生触感的降级方案。
模块对外暴露的 TypeScript 入口为 packages/expo-haptics/src/Haptics.ts,其导出与类型定义(packages/expo-haptics/src/Haptics.types.ts)完全对齐,便于 DOM 组件与@expo/dom-webview场景下的统一使用(该对齐工作记录在模块 CHANGELOG.md 的 14.0.0 版本说明中)。
二、安装与平台配置
2.1 托管(Managed)Expo 项目
对于托管项目,直接使用 Expo CLI 的版本对齐命令安装即可,它会自动选择与当前 SDK 匹配的版本:
npx expo install expo-haptics安装后无需任何手动配置,模块即可开箱即用。
2.2 纯 React Native(Bare)项目
纯 React Native 项目需要先确保已经安装并配置好expo包,然后按以下步骤操作:
第一步:添加 npm 依赖
npx expo install expo-haptics第二步:配置 Android
该模块需要控制设备振动的权限,权限会在构建时自动添加。查看模块自带的 packages/expo-haptics/android/src/main/AndroidManifest.xml,可以看到它声明了:
<uses-permission android:name="android.permission.VIBRATE"/>也就是说,你在自己的AndroidManifest.xml中无需手动重复声明。不过,如果你的应用同时在其他地方手动控制了振动权限,也可以显式声明(与自动添加的声明等价,不会冲突):
<!-- Added permissions --> <uses-permission android:name="android.permission.VIBRATE" />第三步:配置 iOS
iOS 端安装 npm 包后,需要重新安装 CocoaPods 依赖:
npx pod-install值得注意的是,VIBRATE权限仅对基于Vibrator的旧式振动 API 有意义。模块在 Android 14.1.0 版本起新增的performAndroidHapticsAsync(调用View.performHapticFeedback)不再依赖VIBRATE权限,这一点在 Haptics.ts 的文档注释中被明确强调,详见下文 API 详解。
三、核心 API 详解
模块共暴露 4 个异步方法,均返回Promise<void>,并在原生能力不可用时抛出UnavailabilityError(见 packages/expo-haptics/src/Haptics.ts 中的守卫逻辑)。
3.1notificationAsync(type?):通知级别反馈
用于表达任务结果类语义,如操作成功、警告、失败。默认值为NotificationFeedbackType.Success。
import * as Haptics from 'expo-haptics'; // 操作成功后 await Haptics.notificationAsync(Haptics.NotificationFeedbackType.Success); // 数据校验警告 await Haptics.notificationAsync(Haptics.NotificationFeedbackType.Warning); // 请求失败 await Haptics.notificationAsync(Haptics.NotificationFeedbackType.Error);3.2impactAsync(style?):碰撞冲击反馈
模拟 UI 元素之间发生碰撞的冲击感,常用于按钮按下、卡片被拖动等交互。默认值为ImpactFeedbackStyle.Medium。
import * as Haptics from 'expo-haptics'; await Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light); await Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Medium); await Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Heavy); await Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Rigid); await Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Soft);3.3selectionAsync():选择变更反馈
用于在用户切换选择项(如滚动选择器、切换 Tab、滑块移动)时给出轻量确认,无参数:
import * as Haptics from 'expo-haptics'; await Haptics.selectionAsync();3.4performAndroidHapticsAsync(type):Android 专用系统触觉
这是 Android 平台推荐的新式触觉 API(Android 14.1.0 引入,见 CHANGELOG.md)。它直接调用View.performHapticFeedback,效果与 iOS 触觉反馈类似,且不需要VIBRATE权限:
import * as Haptics from 'expo-haptics'; // 仅 Android 生效;其他平台直接返回 await Haptics.performAndroidHapticsAsync(Haptics.AndroidHaptics.Confirm);从 Haptics.ts 的源码可以看到,该函数在非 Android 平台会直接return,不会抛错,因此可以安全地在跨平台代码中调用。
3.5 枚举类型对照表
NotificationFeedbackType(通知反馈)
| 枚举值 | 说明 |
|---|---|
Success | 任务成功完成 |
Warning | 任务产生警告 |
Error | 任务失败 |
ImpactFeedbackStyle(冲击反馈强度)
| 枚举值 | 说明 |
|---|---|
Light | 小型、轻量 UI 元素之间的碰撞 |
Medium | 中等尺寸 UI 元素之间的碰撞 |
Heavy | 大型、重型 UI 元素之间的碰撞 |
Soft | 柔软、弹性大的碰撞 |
Rigid | 坚硬、弹性小的碰撞 |
AndroidHaptics(Android 系统预置触觉,共 22 种)
| 枚举值 | 触发场景 |
|---|---|
Confirm | 确认或成功完成用户交互 |
Reject | 拒绝或失败 |
Gesture_Start/Gesture_End | 手势开始 / 结束(如软键盘) |
Toggle_On/Toggle_Off | 开关切换到开 / 关 |
Clock_Tick | 时钟刻度按下 |
Context_Click | 上下文点击 |
Drag_Start | 拖拽开始(拖拽目标被"拿起") |
Keyboard_Tap/Keyboard_Press/Keyboard_Release | 软键盘按键按下 / 释放 |
Long_Press | 长按触发动作 |
Virtual_Key/Virtual_Key_Release | 虚拟按键按下 / 释放 |
No_Haptics | 不执行任何触觉反馈 |
Segment_Tick | 在少量候选项之间切换(如列表项、滑块离散点) |
Segment_Frequent_Tick | 在大量候选项之间快速切换(如时钟分钟刻度),设计为极轻、可高频触发,若设备无法产生足够轻柔的振动则可能不振动 |
Text_Handle_Move | 文本选区 / 插入点手柄移动 |
完整枚举定义见 packages/expo-haptics/src/Haptics.types.ts。
四、源码级原理:三大平台的底层实现
4.1 iOS:基于 UIKit 的三种 Feedback Generator
iOS 实现位于 packages/expo-haptics/ios/HapticsModule.swift,模块名注册为ExpoHaptics。三个异步函数分别对应系统三套触觉生成器:
notificationAsync→UINotificationFeedbackGenerator,先调用prepare()预热,再按类型触发notificationOccurred(.success / .warning / .error);impactAsync→UIImpactFeedbackGenerator(style:),prepare()后调用impactOccurred(),其中light / medium / heavy / soft / rigid直接映射到UIImpactFeedbackGenerator.FeedbackStyle的五个枚举值;selectionAsync→UISelectionFeedbackGenerator,prepare()后调用selectionChanged()。
三个函数都通过.runOnQueue(.main)强制在主线程执行。这一点非常关键——历史版本中曾因不在主线程调用 Feedback Generator 而导致 iOS 偶发崩溃(该修复记录在 CHANGELOG.md 12.0.1 版本中),因此在自定义原生实现时务必遵循"主线程调用 + 提前 prepare"的规范。
4.2 Android:Vibrator 波形模拟与系统触觉反馈
Android 实现位于 packages/expo-haptics/android/src/main/java/expo/modules/haptics/HapticsModule.kt。
Vibrator 的获取做了版本适配:Android 12(API 31,Build.VERSION_CODES.S)及以上通过VibratorManager.defaultVibrator获取;更早版本则使用旧式Context.VIBRATOR_SERVICE(带@Suppress("DEPRECATION")标注)。
振动波形的生成(vibrate私有方法)同样按 API 版本分流:
- API 26(Android 8.0 /
Build.VERSION_CODES.O)及以上:使用VibrationEffect.createWaveform(timings, amplitudes, -1),其中-1表示不重复、只振动一次; - 更早版本:退化为
vibrator.vibrate(pattern, -1)的旧式纯时长模式。
每种反馈类型都预置了精确的振动参数(时间 ms 与振幅 0–255),位于 packages/expo-haptics/android/src/main/java/expo/modules/haptics/arguments/ 下:
HapticsImpactType.kt定义的冲击反馈波形(timings/amplitudes/ 旧 SDK 兼容 pattern):
| 类型 | timings (ms) | amplitudes (0–255) |
|---|---|---|
light/soft | [0, 50] | [0, 30] |
medium/rigid | [0, 43] | [0, 50] |
heavy | [0, 60] | [0, 70] |
HapticsNotificationType.kt定义的通知反馈波形:
| 类型 | timings (ms) | amplitudes (0–255) |
|---|---|---|
success | [0, 40, 100, 40] | [0, 50, 0, 60] |
warning | [0, 40, 120, 60] | [0, 40, 0, 60] |
error | [0, 60, 100, 40, 80, 50] | [0, 50, 0, 40, 0, 50] |
其中timings数组的元素按"振动—停顿"交替解释:如[0, 40, 100, 40]表示立即开始振动 40ms、停顿 100ms、再振动 40ms。非法参数(如字符串拼写错误)会抛出HapticsInvalidArgumentException,其消息会明确列出合法取值。
performHapticsAsync的特殊处理:该方法通过appContext.currentActivity找到内容视图(android.R.id.content)并调用view.performHapticFeedback(...)。由于该方法必须运行在主线程(main-thread affine),而模块默认调度线程上调用会静默无效,因此源码中显式使用了.runOnQueue(Queues.MAIN)——这正是模块 CHANGELOG.md Unpublished 版本中记录的 bug 修复("FixperformAndroidHapticsAsyncdoing nothing by running it on the main queue")的根因与解决方案。
4.3 Web:Web Vibration API 与 iOS Safari 降级
Web 实现位于 packages/expo-haptics/src/ExpoHaptics.web.ts,是理解跨平台降级设计的绝佳样例。
第一步:能力检测。isVibrationAvailable()检查navigator.vibrate是否存在;supportsCoarsePointer通过matchMedia('(pointer: coarse)')判断是否为触屏设备。
第二步:振动模式映射。vibrationPatterns表把各类反馈映射为振动时长序列:
| 反馈类型 | 振动模式 (ms) |
|---|---|
Success | [40, 100, 40] |
Warning | [50, 100, 50] |
Error | [60, 100, 60, 100, 60] |
Light | [40] |
Medium | [50] |
Heavy | [60] |
Soft | [35] |
Rigid | [45] |
selection | [50] |
第三步:iOS Safari 特判。iOS Safari 不支持navigator.vibrate,但系统会对 switch 开关切换提供原生触感。实现利用这一点:动态创建一个隐藏的<input type="checkbox" switch>元素挂到document.head,通过labelEl.click()触发原生触觉反馈后立即移除(iOSSwitchHaptic()函数,该技巧自 55.0.12 版本加入)。对于Error这类需要多次脉冲的反馈,会以 120ms 间隔连续触发多次,模拟更强烈的提醒。
从调用链可以看出,Web 端与原生端共用同一套 TypeScript API(ExpoHaptics.ts通过requireOptionalNativeModule('ExpoHaptics')按平台解析实现),这也是模块"一套 API、三端一致"体验的架构基础。
五、实践建议与注意事项
- Android 上优先使用
performAndroidHapticsAsync。模块在 Haptics.ts 中明确给出官方建议:VibratorAPI 并不适合实现精细的触觉反馈,应优先使用performAndroidHapticsAsync,它更接近 iOS 的触觉体验,且免去了VIBRATE权限要求。 - 注意
impactAsync等旧 API 的跨端差异:同一枚举值在 iOS 上直接映射 UIKit 反馈类型,在 Android 上则被翻译为预设振动波形,在 Web 上被翻译为时长序列——因此"同一种反馈"在三端感知并不完全相同,设计交互时应以真机验证为准。 - iOS 主线程约束:如果在自有原生模块中实现触觉,务必在主线程调用 Feedback Generator 并提前
prepare(),避免偶发崩溃。 - 系统设置感知:触觉反馈的最终表现受系统"触觉/振动"设置影响,代码层面无法绕过;Web 端则需要用户浏览器授予振动能力。
- 版本要求:当前仓库中 expo-haptics 版本为 57.0.1(见 packages/expo-haptics/package.json);56.0.0 起最低 iOS/tvOS 版本提升至 16.4、macOS 13.4,集成时请确认工程的最低系统版本满足要求。
六、参考文件索引
- API 入口与调用链:packages/expo-haptics/src/Haptics.ts
- 类型与枚举定义:packages/expo-haptics/src/Haptics.types.ts
- iOS 原生实现:packages/expo-haptics/ios/HapticsModule.swift
- Android 原生实现:packages/expo-haptics/android/src/main/java/expo/modules/haptics/HapticsModule.kt
- Android 振动参数:
arguments/目录下的 HapticsImpactType.kt、HapticsNotificationType.kt 等 - Web 实现与降级策略:packages/expo-haptics/src/ExpoHaptics.web.ts
- Android 权限声明:packages/expo-haptics/android/src/main/AndroidManifest.xml
- 版本演进记录:packages/expo-haptics/CHANGELOG.md
【免费下载链接】expoAn open-source framework for making universal native apps with React. Expo runs on Android, iOS, and the web.项目地址: https://gitcode.com/GitHub_Trending/ex/expo
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考