navigator.vibrate仅支持单数字参数(如150ms)且须在用户手势事件中同步调用,ios全系不支持,android多机型对数组模式兼容性极差,自定义节奏基本不可行。

navigator.vibrate 在真实移动端 Web 中能用,但只在极少数场景下可靠;所谓“自定义节奏”基本不可行,iOS 完全不支持,Android 数组模式兼容性差到无法落地。
为什么 navigator.vibrate([100, 50, 100]) 几乎必然失效
这个数组本意是「震 100ms → 停 50ms → 震 100ms」,但实际行为完全失控:
- Android 多数机型会静默截断奇数长度数组的最后一项,变成
[100, 50],即「震 100ms → 停 50ms」后直接结束 - 部分低端 Android WebView 无法解析停顿间隔,把整个数组合并成一次长震(如 250ms)
- iOS Safari 所有版本、所有 WKWebView(含微信/钉钉内嵌页)均返回
typeof navigator.vibrate === 'undefined'或'object',永远不是函数,调用无任何效果 - 即使数组合法(偶数长度、总时长 ≤ 5000ms、项数 ≤ 10),只要首项为 0,整个调用会被忽略
真正能落地的写法只有 navigator.vibrate(150)
单数字参数是目前唯一跨 Android 主流机型稳定生效的方式:
-
navigator.vibrate(150)被所有支持该 API 的浏览器统一解释为「持续震动 150 毫秒」,无歧义、无截断、无静默失败 - 适合表单提交成功、开关切换完成、关键按钮点击等需明确确认的场景
- 避免用
100或200这类边界值:太短(200ms)易被误判为系统异常 - 不要封装成「震动等级」抽象层——不同设备硬件差异大,同一数值在旗舰机和千元机上手感可能差一倍
用户手势触发是硬性前提,不是可选项
哪怕检测到 navigator.vibrate 存在且是函数,只要调用不在用户真实交互事件中同步执行,就会静默失败:
- ❌
setTimeout(() => navigator.vibrate(150), 0) - ❌
fetch('/api').then(() => navigator.vibrate(150)) - ❌ 在
DOMContentLoaded或 Vue 的mounted钩子里提前调用 - ✅ 正确写法必须绑定到用户事件处理器内:
button.addEventListener('click', () => { if ('vibrate' in navigator && typeof navigator.vibrate === 'function') { navigator.vibrate(150); } }); - ⚠️ iframe 内调用需显式声明
allow="vibrate",否则无效
iOS 上别白费力气做 fallback
检测逻辑必须严格写成:'vibrate' in navigator && typeof navigator.vibrate === 'function'。仅靠 'vibrate' in navigator 会误判 iOS —— 它返回 true,但 navigator.vibrate 不是函数。
更关键的是:iOS 不支持该 API 是系统级限制,不是浏览器 bug。不要试图:
- 在 else 分支里加音效或动画作为“触感 fallback”——这属于交互设计决策,和震动 API 无关
- 用
try/catch包裹调用——它根本不会抛错,只会静默跳过 - 给
document.body加touchstart监听器再全局触发震动——违反用户手势同步要求,依然无效
复杂点在于:震动效果无法预知硬件响应,同一段代码在不同 Android 设备上可能震感差异极大;而 iOS 完全缺席,意味着你必须接受「触感反馈在部分设备上天然缺失」这个事实,而不是把它当成一个待解决的兼容性问题。










