如何解決 touchstart 事件與 click 事件的衝突

一 · 業務場景的描述

在對已完成的PC站點進行移動端適配時,咱們想要站點在移動設備上有更快的響應速度,以帶給用戶更好的體驗,此時,咱們應該使用移動設備專用的事件系統,例如,使用
touchstart 事件代替 click 事件。瀏覽器

爲何這樣效果會更好呢?根據Google開發者文檔中的描述:app

mobile browsers will wait approximately 300ms from the time that you tap the button to fire the click event. The reason for this is that the browser is waiting to see if you are actually performing a double tap.

移動設備上的瀏覽器將會在 click 事件觸發時延遲 300ms ,以確保這是一個「單擊」事件而非「雙擊」事件。函數

而對於 touchstart 事件而言,則會在用戶手指觸碰屏幕的一瞬間觸發所綁定的事件。因此,使用 touchstart 替換 click 事件的意義在於,幫助用戶在每次點擊時節省 300ms 的時間。在頁面頻繁須要點擊,或者點擊發生在動畫中,對動畫流暢度有較高要求的情境下,使用這種技術是很是必要的。動畫

可是,讓咱們回到咱們的初始場景,在 PC端站點適配移動端時 咱們不能簡單的進行 touchstartclick 事件的替換,由於PC並不能識別 touchstart 事件。this

二 · 產生衝突的緣由

固然,咱們能夠給某個元素同時綁定 touchstartclick 事件,但這將會致使本篇文章解決的問題 -- 這兩個事件在移動設備上會發生衝突code

因爲移動設備可以同時識別 touchstartclick 事件,所以當用戶點擊目標元素時,綁定在目標元素上的 touchstart 事件與 click 事件(約300ms後)會依次被觸發,也就是說,咱們所綁定的回調函數會被執行兩次!。這顯然不是咱們想要的結果。orm

三 · 解決方案

針對這樣的情境,有如下兩種解決方案:對象

(一)使用 preventDefault

第一種解決方案是使用事件對象中的 preventDefault 方法,對於該方法MDN上的解釋是:事件

The Event interface's preventDefault() method tells the user agent that if the event does not get explicitly handled, its default action should not be taken as it normally would be.

可見, preventDefault 方法的做用在於:阻止元素默認事件行爲的發生,但有意思的是,當咱們在目標元素同時綁定 touchstartclick 事件時,在 touchstart 事件回調函數中使用該方法,能夠阻止後續 click 事件的發生。ci

這從道理上是講不通的,畢竟,咱們添加的 click 事件並非元素的「默認事件」,但它確實奏效了,或者說,被瀏覽器實現了,所以咱們可使用該方法解決移動設備上 touchstart 事件與 click 事件的衝突問題,具體代碼以下:

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 事件來封裝元素的點擊事件,這樣客戶端會根據當前環境斷定元素應該綁定的事件類型,代碼以下:

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!")
})

總結

該方法的優勢在於,咱們經過增長一次判斷,爲元素減小了一個沒必要要的事件綁定,從而避免了 touchstartclick 事件的衝突問題。這種方法避免了咱們書寫兩次一樣的代碼,而且相較於第一種方法更加符合邏輯,所以是我所推薦的。

相關文章
相關標籤/搜索