Web:移动端阻止默认行为的小坑


问题

移动端 web 开发中,使用 addEventListener 阻止了 touchstart 事件的默认行为却发现没有生效

描述

再移动端 web 开发中,我们一般会用 addEventListener 给 document 节点的 touchstart 事件设置 preventDefault() 方法以达到禁用事件默认行为的目的,但有时候会发现在 chrome 移动端模拟器或者手机浏览器上事件的默认行为并没有成功禁用。

document.addEventListener("touchstart", function (ev) {
    ev = ev || event;
    ev.preventDefault();
});

原因

首先来看下 MDN 中 addEventListener() 方法的描述:

EventTarget.addEventListener()

EventTarget.addEventListener() 方法将指定的监听器注册到 EventTarget 上,当该对象触发指定的事件时,指定的回调函数就会被执行。 事件目标可以是一个文档上的元素 Element,DocumentWindow或者任何其他支持事件的对象 (比如 XMLHttpRequest)

addEventListener()的工作原理是将实现EventListener的函数或对象添加到调用它的EventTarget上的指定事件类型的事件侦听器列表中。

语法

target.addEventListener(type, listener, options);
target.addEventListener(type, listener, useCapture);
target.addEventListener(type, listener, useCapture, wantsUntrusted );  // Gecko/Mozilla only

addEventListener() 除了事件类型 type 和 回调函数 listenner 外,还有一个可选参数 optionsoptions 传入一个可选参数对象,主要包括四个参数,其中preventDefault() 不生效问题就是有 passive 这个参数引起的。

tartget.addEventListenner(type, listener, {
    capture: Booolean,
    once: Boolean,
    passive: Boolean,
    signal: AbortSignal
})

passive 用于控制是否调用 preventDefault() ,在以前,passive 默认是为 true,即 addEventListener() 中写了 event.preventDefault() 会被正常调用。后来,人们发现 passive 设置为 true 会降低滚屏性能。为什么呢?事件监听器在监听事件时,并不能提前知道回调函数中是否会阻止默认行为,因此若想知道是否会阻止就需要等待函数执行完,这段时间虽然很短,但等待仍会让人感到卡顿。卡顿对比请看下图。而大部分事件监听器是不会阻止默认行为的,所以大部分情况下页面因为等待是否会有 preventDefault() 是完全没必要的,会影响大部分情况下的体验。为解决卡顿问题,某些浏览器就将一些节点事件的 passive 默认为 true,即使函数中使用了 preventDefault() 也会被无视,因为看到 preventDefault() 时浏览器可能已经执行了默认行为,总不能撤回吧(doge) 。

解决方案

前面扯了这么多,但解决很简单,把 passive 设置为 false 传进监听器就行了

document.addEventListener("touchstart", function (ev) {
    ev = ev || event;
    ev.preventDefault();
}, {passive: false});

参考文献

1. MDN Web Docs:EventTarget.addEventListener()

2. MDN Web Docs:使用 passive 改善的滚屏性能