必须加 passive: true 的三类编辑器场景:contenteditable 外层容器 scroll 监听、textarea+覆盖层代码编辑器的 touchmove 拖拽、富文本侧边栏 overflow: auto 容器;凡不调用 e.preventdefault() 的监听均需设置 passive: true。

给 HTML 编辑器(比如 contenteditable 区域或自定义滚动容器)加 scroll 或 touchmove 监听时,不设 { passive: true } 就大概率卡顿——这不是“可能”,是 Chrome/Firefox/Safari 当前(2026 年)的确定行为。
哪些编辑器场景必须加 passive: true
不是所有滚动监听都需要它,但以下三类编辑器交互一旦漏掉,用户立刻感知卡顿:
-
contenteditable元素外层容器监听scroll做虚拟滚动或光标位置同步 - 基于
textarea+ 覆盖层实现的代码编辑器(如简易 Monaco 替代),监听touchmove实现手势拖拽滚动 - 富文本编辑器中可滚动的侧边栏(如样式面板、组件库),其容器设置了
overflow: auto
关键判断标准:只要监听器里没调用 e.preventDefault(),就该上 passive: true。编辑器里绝大多数滚动监听都符合这点——你只是读取 scrollTop 或计算光标坐标,不是拦截滚动本身。
addEventListener 的 passive 参数写法和常见错误
必须用对象参数形式,且不能和旧式布尔参数混用。下面这些写法在编辑器里高频出错:
- ✅ 正确:
editorContainer.addEventListener('scroll', onScroll, { passive: true }); - ❌ 错误:
editorContainer.addEventListener('scroll', onScroll, true);—— 这是把useCapture当成passive,浏览器完全忽略优化 - ❌ 错误:
editorContainer.addEventListener('touchmove', onTouchMove, { passive: true, capture: true });——capture: true在touchmove中极少必要,反而增加事件分发开销,编辑器里基本不用 - ❌ 错误:
document.addEventListener('touchmove', preventDefaultAll, { passive: true });—— 既设了passive: true又在回调里写e.preventDefault(),Chrome 控制台会报 “Unable to preventDefault inside passive event listener”,且滚动仍卡
兼容性与降级:Safari 11.1+ 和 Edge 79+ 都支持,但得检测
虽然主流浏览器已全面支持,但 Safari 11.0(iOS 11.0)及更早版本不识别 passive,直接传对象会报错。必须做特性检测:
const passiveIfSupported = (function () {
let supportsPassive = false;
try {
const opts = Object.defineProperty({}, 'passive', {
get() {
supportsPassive = true;
}
});
window.addEventListener('test', null, opts);
} catch (e) {}
return supportsPassive ? { passive: true } : false;
})();
// 使用
if (passiveIfSupported) {
editorContainer.addEventListener('scroll', handleScroll, passiveIfSupported);
} else {
editorContainer.addEventListener('scroll', handleScroll);
}
注意:不要用 UA 判断,'passive' in addEventListener 不可靠(因为 addEventListener 是函数,不是对象);必须用 Object.defineProperty 检测属性可枚举性。
编辑器里最容易被忽略的点:scroll 监听器也要节流
加了 passive: true 只解决“等待 JS 执行”这一步,但滚动事件本身仍是高频触发(尤其 touchmove)。编辑器里如果每次 scroll 都重算光标位置、更新行号、触发语法高亮,照样卡。
建议搭配轻量节流(比如 16ms 一次):
let scrollTimer;
editorContainer.addEventListener('scroll', () => {
clearTimeout(scrollTimer);
scrollTimer = setTimeout(() => {
updateCursorLine();
syncLineNumber();
}, 16);
}, passiveIfSupported);
真正卡住编辑器滚动的,往往不是 passive 缺失,而是 passive 加了但后续逻辑没控频——这点比加 passive 本身还容易被忽略。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











