如何解決 touchstart 事件與 click 事件的衝突
一 · 業務場景的描述
在對已完成的PC站點進行移動端適配時,我們想要站點在移動裝置上有更快的響應速度,以帶給使用者更好的體驗,此時,我們應該使用移動裝置專用的事件系統,例如,使用 touchstart 事件代替 click 事件。
為什麼這樣效果會更好呢?根據Google開發者文件中的描述:
移動裝置上的瀏覽器將會在 click 事件觸發時延遲 300ms ,以確保這是一個“單擊”事件而非“雙擊”事件。
而對於 touchstart 事件而言,則會在使用者手指觸碰螢幕的一瞬間觸發所繫結的事件。所以,使用 touchstart 替換 click 事件的意義在於,幫助使用者在每次點選時節省 300ms 的時間。在頁面頻繁需要點選,或者點擊發生在動畫中,對動畫流暢度有較高要求的情境下,使用這種技術是非常必要的。
但是,讓我們回到我們的初始場景,在 PC端站點適配移動端時 我們不能簡單的進行 touchstart和 click 事件的替換,因為PC並不能識別 touchstart 事件。
二 · 產生衝突的原因
當然,我們可以給某個元素同時繫結 touchstart 和 click 事件,但這將會導致本篇文章解決的問題 – 這兩個事件在移動裝置上會發生衝突。
由於移動裝置能夠同時識別 touchstart 和 click 事件,因此當用戶點選目標元素時,繫結在目標元素上的 touchstart 事件與 click 事件(約300ms後)會依次被觸發,也就是說,我們所繫結的回撥函式會被執行兩次!。這顯然不是我們想要的結果。
三 · 解決方案
針對這樣的情境,有以下兩種解決方案:
(一)使用 preventDefault
第一種解決方案是使用事件物件中的 preventDefault 方法,preventDefault 方法的作用在於:阻止元素預設事件行為的發生,但有意思的是,當我們在目標元素同時繫結 touchstart 和 click 事件時,在 touchstart 事件回撥函式中使用該方法,可以阻止後續 click 事件的發生。
這從道理上是講不通的,畢竟,我們新增的 click 事件並不是元素的“預設事件”,但它確實奏效了,或者說,被瀏覽器實現了,因此我們可以使用該方法解決移動裝置上 touchstart 事件與 click 事件的衝突問題,具體程式碼如下:
1
2
3
4
5
6
7
8
9
10
11
const Button = document.getElementById(“targetButton”)
Button.addEventListener(“touchstart”, e => {
e.preventDefault()
console.log(“touchstart event!”)
})
Button.addEventListener(“click”, e => {
e.preventDefault()
console.log(“click event!”)
})
當你在瀏覽器上模擬移動裝置後點擊目標元素,只會在控制檯看到 touchstart event! 欄位,很顯然,click 事件被成功阻止了。
總結
使用該方法的優點在於簡單粗暴,直接有效,能夠很好的實現我們的目標,但缺點在於, preventDefault 方法為阻止 click 事件的方式是瀏覽器實現上的,而不是 preventDefault 原理上的,這會帶來一些不確定性,雖然我暫時尚未發現該方法失效的具體場景。
(二)基於功能檢測繫結事件
我們可以通過判斷瀏覽器是否支援 touchstart 事件來封裝元素的點選事件,這樣客戶端會根據當前環境判定元素應該繫結的事件型別,程式碼如下:
1
2
3
4
5
6
7
8
9
10
11
12
const Button = document.getElementById(“targetButton”)
const clickEvent = (function() {
if (‘ontouchstart’ in document.documentElement === true)
return ‘touchstart’;
else
return ‘click’;
})();
Button.addEventListener(clickEvent, e => {
console.log(“things happened!”)
})
總結
該方法的優點在於,我們通過增加一次判斷,為元素減少了一個不必要的事件繫結,從而避免了 touchstart 與 click 事件的衝突問題。這種方法避免了我們書寫兩次同樣的程式碼,並且相較於第一種方法更加符合邏輯,因此是我所推薦的。