Web:移動端阻止預設行為的小坑

WindChen發表於2022-02-08

問題

移動端 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) 。

Web:移動端阻止預設行為的小坑

解決方案

前面扯了這麼多,但解決很簡單,把 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 改善的滾屏效能

相關文章