必须加 {passive: true},否则浏览器会同步等待 touchmove 监听器执行以判断是否调用 preventDefault(),导致滚动卡顿;需用对象传参且运行时检测兼容性。

加 {passive: true} 是必须的,否则移动端 touchmove/wheel 会卡顿,且 Chrome/Firefox 已默认对 document 级 touch 事件启用 passive,不显式声明反而会触发警告。
为什么 touchmove 不加 passive 就会卡
浏览器看到 touchmove 监听器,第一反应是“先等你执行完,再决定滚不滚”——哪怕监听器里只写了 console.log(),也会阻塞主线程。这不是代码慢,是浏览器策略:它必须确保你没调 preventDefault()。而 80% 的 touchmove 监听器其实根本不需要拦截滚动,纯属白等。
- 现象:手指一滑页面就“粘滞”,松手后才突然跟上,尤其在低端 Android 设备上明显
- 原因:未声明
passive时,浏览器按老逻辑同步等待监听器返回,拖慢渲染帧率 - 注意:这个卡顿和 JS 执行耗时无关,空函数也卡;不是 bug,是规范行为
addEventListener 第三个参数怎么传才算对
必须传对象,不能传布尔值,也不能漏掉其他可能需要的选项(比如 capture)。
- ✅ 正确:
el.addEventListener('touchmove', handler, { passive: true }) - ❌ 错误:
el.addEventListener('touchmove', handler, true)—— 这里的true被当成了旧版useCapture参数,passive根本没生效 - ⚠️ 注意:
{ passive: true, capture: true }合法,但捕获阶段无法调preventDefault(),慎用 - 兼容 fallback:检测到不支持
passive时,直接不传第三个参数(回退到老行为),别硬塞{ passive: false }
什么时候必须设 passive: false
只有当你真要阻止默认滚动时才需要,比如下拉刷新、横向滑动轮播、手势拖拽等场景。
将调研报告、方案对比、数据分析、需求梳理、会议纪要、设计说明、代码评审、学习笔记等任务产物输出为可在浏览器直接打开的单文件 HTML,替代 Markdown 作为最终交付物。Markdown 在信息密度、视觉清晰度和可分享性上已跟不上 AI 产物的丰富度,HTML 能同时承载排版、表格、SVG 等。
立即学习“前端免费学习笔记(深入)”;
- 典型错误:给整个
document绑touchstart并设{ passive: false },结果全局滚动都变卡 - 正确做法:只在具体容器上设,例如
refresher.addEventListener('touchstart', onPullStart, { passive: false }) - 风险提示:iOS Safari 12.2+ 才支持
passive: false,旧版本会静默忽略,导致preventDefault()失效 - Vue/React 注意:
v-on:touchmove.passive或onTouchMove默认不带passive,得手动用ref绑原生事件
如何安全检测 passive 是否可用
不能看 UA,必须运行时检测。靠 Object.defineProperty 拦截 passive 属性访问是最可靠方式。
let supportsPassive = false;
try {
const opts = Object.defineProperty({}, 'passive', {
get() { supportsPassive = true; }
});
window.addEventListener('test', null, opts);
} catch (e) {}
// 后续可基于 supportsPassive 做分支
- 检测失败时,fallback 方案只能是省略第三个参数(即默认
passive: false行为) - 别用
'ontouchstart' in window代替,这和passive支持度无关 - 真实项目中建议封装一个
addPassiveListener工具函数,统一处理兼容逻辑
最常被忽略的一点:passive 不是“开了就完事”的开关,它和事件是否真的需要拦截默认行为强绑定。设错地方或设错值,要么卡顿照旧,要么交互失效——得对着每个监听器问一句:“这里到底要不要 preventDefault()?”


















