先记住这个答案
要阻止滚轮触发页面滚动,应使用 addEventListener('wheel', handler, { passive: false }),并在 handler 内调用 preventDefault()。之后浏览器不再执行默认滚动,但必须自行处理滚动动画,例如更新 scrollTop 或使用变换实现自定义滚动。同时注意 wheel 事件可取消,但某些浏览器在连续滚动序列的后几个事件可能不可取消,且 passive: true 会使 preventDefault() 失效并产生警告。
- 设置
passive: false才能取消wheel事件 - 阻止默认滚动后需自行实现滚动动画
- 连续滚动序列中部分事件可能不可取消
wheel 事件与默认滚动的关系
wheel 事件在用户滚动鼠标滚轮或触摸板时触发,它是一个可取消事件(cancelable 属性通常为 true)。浏览器在派发该事件后,会检查事件是否被 preventDefault() 取消,若未取消则执行默认的滚动或缩放行为。因此,在监听器内调用 preventDefault() 是阻止页面滚动的标准方式。
但关键在于 addEventListener 的 passive 选项。当 passive 为 true 时,表示监听器不会调用 preventDefault(),从而让浏览器无需等待 JS 执行即可立即滚动,以提高性能。若此时强行调用 preventDefault(),不仅无效,还会在控制台产生警告。要禁止默认滚动,必须显式设置 { passive: false }。
自定义横屏滚动容器
场景:一个横向滚动的照片画廊,设计为使用鼠标滚轮垂直滚动来驱动水平移动,同时阻止页面原本的垂直滚动。输入:用户滚动滚轮,产生 wheel 事件,deltaY 为正表示向下。约束:页面主体不能因滚轮而上下移动,但容器需要根据 deltaY 增加或减少 scrollLeft。
处理:在容器上添加 addEventListener('wheel', handler, { passive: false }),handler 内调用 preventDefault(),然后更新 container.scrollLeft += deltaY。结果页面不再滚动,画廊水平滑动。需要自行处理惯性效果吗?不,默认无惯性,可附加动画。此方式避免了 scroll 事件的异步性,让位移与滚轮同步。
易失效条件与负滚动链
第一个风险是 passive 默认为 true 的情况并非所有浏览器都强制,Chrome 对 wheel 事件默认已为 true,touchstart/touchmove 也已默认 true。若通过网络库或框架绑定事件,可能默认设置了 passive,导致 preventDefault 静默失败。可用 addEventListener 显式传 { passive: false }。
第二个风险是“负滚动链”:当容器内部已到达滚动边界,滚轮继续滚动,事件默认会传播到父级进行“链式滚动”。若子容器调用了 preventDefault,则父级也不会滚动,但若子容器没有处理边界条件(如 scrollLeft 为 0 或最大),再阻止默认也会阻断页面整体滚动。因此要检查 scrollable 状态,在到达边界时释放事件,允许后续默认行为。但释放意味着需移除 preventDefault,可动态判断。代价是逻辑复杂化,需监听 wheel 并判断定位。
容易答错的地方
- 以为只要调用 preventDefault 即可生效
- 若不将 listener 设为
passive: false,尤其在某些开发库或已设置 passive 的场景,preventDefault调用被忽略,页面仍滚动。必须检查 addEventListener 的 options 对象。依据规范,passive 监听器中 preventDefault 不产生效果。 - 忽略滚动链,在子元素无边界时也禁止滚动
- 若子容器内内容不足或已到边界,仍调用 preventDefault 会阻止父容器滚动,导致页面整体卡死。正确做法是判断滚动位置是否还有空间,若无则不再 preventDefault,让滚轮默认行为继续。
面试官还会怎么问?
在 React 中如何绑定 wheel 事件并取消默认滚动?
React 的合成事件对 wheel 默认 passive 为 false,但需在监听函数中调用 event.preventDefault()。若在根元素使用文档级别,建议直接在原生节点上 addEventListener 并传 { passive: false },避免依赖合成事件。注意 React 17 后绑定到 root 容器,事件代理可能影响。
wheel 事件与 touchpad 的 inertia 效果如何处理?
preventDefault 会停止所有默认滚动,包括触摸板的惯性滚动。若需保留惯性,需自行计算速度并模拟,例如基于 deltaY 累积加速度进行动画。无法直接控制浏览器自身的惯性,只能实现类似的物理模拟。
如何判断当前 wheel 事件是否可能导致页面滚动(即是否存在滚动链)?
可检查 event.cancelable,但更可靠是判断当前元素及祖先是否有可滚动方向。需要检测 scrollHeight 与 clientHeight 差异,以及定位。但无标准同步方法,通常需要自行计算滚动边界。
参考资料
示例用于理解所注明的运行环境与边界;延伸学习可结合原文中的更多案例。