天天看點

移動端touch事件和滑鼠事件

前言

今天解決項目中一個移動端H5點選拖拽問題,發現元件中使用的 onmousemove 和 onmouseup事件。由此産生移動端touch事件和mouse事件有何不同的疑問,看完這篇文章感覺很有幫助。以下為轉載内容:https://www.cnblogs.com/xiaoxingyiyi/p/5447378.html

touch事件的來源

PC網頁上的大部分操作都是用滑鼠的,即響應的是滑鼠事件,包括mousedown、mouseup、mousemove和click事件。一次點選行為,可被拆解成:mousedown -> click -> mouseup三步。

手機上沒有滑鼠,是以就用觸摸事件去實作類似的功能。touch事件包含touchstart、touchmove、touchend,注意手機上并沒有tap事件。手指觸發觸摸事件的過程為:touchstart -> touchmove -> touchend。

手機上沒有滑鼠,但不代表手機不能響應mouse事件(其實是借助touch去觸發mouse事件)。有人在PC和手機上對事件做了對比實驗,以說明手機對touch事件相應速度快于mouse事件。

移動端touch事件和滑鼠事件

可以看到在手機上,當我們手觸碰螢幕時,要過300ms左右才會觸發mousedown事件,是以click事件在手機上看起來就像慢半拍一樣。

touch事件中可以擷取以下參數

參數 含義
touches 螢幕中每根手指資訊清單
targetTouches 和touches類似,把同一節點的手指資訊過濾掉
changedTouches 響應目前事件的每根手指的資訊清單

tap是怎麼來的

用過Zepto或KISSY等移動端js庫的人肯定對tap事件不陌生,我們做PC頁面時綁定click,相應地手機頁面就綁定tap。但原生的touch事件本身是沒有tap的,js庫裡提供的tap事件都是模拟出來的。

我們在上面看到,手機上響應 click 事件會有300ms的延遲,那麼這300ms到底是幹嘛了?浏覽器在 touchend 後會等待約300ms,原因是判斷使用者是否有輕按兩下(double tap)行為。如果沒有 tap 行為,則觸發 click 事件,而輕按兩下過程中就不适合觸發 click 事件了。由此可以看出 click 事件觸發代表一輪觸摸事件的結束。

點選穿透現象

有了以上的基礎,我們就可以了解為什麼會出現點選穿透現象了。我們經常會看到“彈窗/浮層”這種東西,我做個了個demo。

移動端touch事件和滑鼠事件

整個容器裡有一個底層元素的div,和一個彈出層div,為了讓彈出層有模态框的效果,我又加了一個遮罩層。

<div class="container">
    <div id="underLayer">底層元素</div>

    <div id="popupLayer">
        <div class="layer-title">彈出層</div>
        <div class="layer-action">
            <button class="btn" id="closePopup">關閉</button>
        </div>
    </div>
</div>
<div id="bgMask"></div>
           

然後為底層元素綁定 click 事件,而彈出層的關閉按鈕綁定 tap 事件。

$('#closePopup').on('tap', function(e){
    $('#popupLayer').hide();
    $('#bgMask').hide();
});

$('#underLayer').on('click', function(){
    alert('underLayer clicked');
});
           

點選關閉按鈕,touchend首先觸發tap,彈出層和遮罩就被隐藏了。touchend後繼續等待300ms發現沒有其他行為了,則繼續觸發click,由于這時彈出層已經消失,是以目前click事件的target就在底層元素上,于是就alert内容。整個事件觸發過程為 touchend -> tap -> click。

而由于click事件的滞後性(300ms),在這300ms内上層元素隐藏或消失了,下層同樣位置的DOM元素觸發了click事件(如果是input框則會觸發focus事件),看起來就像點選的target“穿透”到下層去了。

結合Zepto源碼的解釋

zepto中的 tap 通過兼聽綁定在 document 上的 touch 事件來完成 tap 事件的模拟的,是通過事件冒泡實作的。在點選完成時(touchstart / touchend)的 tap 事件需要冒泡到 document 上才會觸發。而在冒泡到 document 之前,手指接觸和離開螢幕(touchstart / touchend)是會觸發 click 事件的。

因為 click 事件有延遲(大概是300ms,為了實作safari的輕按兩下事件的設計),是以在執行完 tap 事件之後,彈出層立馬就隐藏了,此時 click 事件還在延遲的 300ms 之中。當 300ms 到來的時候,click 到的其實是隐藏元素下方的元素。

如果正下方的元素有綁定 click 事件,此時便會觸發,如果沒有綁定 click 事件的話就當沒發生。如果正下方的是 input 輸入框(或是 select / radio / checkbox),點選預設 focus 而彈出輸入鍵盤,也就出現了上面的“點透”現象。

穿透的解決辦法

1. 遮擋

由于 click 事件的滞後性,在這段時間内原來點選的元素消失了,于是便“穿透”了。是以我們順着這個思路就想到,可以給元素的消失做一個fade效果,類似jQuery裡的fadeOut,并設定動畫duration大于300ms,這樣當延遲的 click 觸發時,就不會“穿透”到下方的元素了。

同樣的道理,不用延時動畫,我們還可以動态地在觸摸位置生成一個透明的元素,這樣當上層元素消失而延遲的click來到時,它點選到的是那個透明的元素,也不會“穿透”到底下。在一定的timeout後再将生成的透明元素移除。具體可見demo

2. pointer-events

pointer-events是CSS3中的屬性,它有很多取值,有用的主要是auto和none,其他屬性值為SVG服務。

取值 含義

auto 效果和沒有定義 pointer-events 屬性相同,滑鼠不會穿透目前層。

none 元素不再是滑鼠事件的目标,滑鼠不再監聽目前層而去監聽下面的層中的元素。但是如果它的子元素設定了pointer-events為其它值,比如auto,滑鼠還是會監聽這個子元素的。

關于使用 pointer-events 後的事件冒泡,有人做了個實驗,見代碼

是以解決“穿透”的辦法就很簡單,demo如下

$(’#closePopup’).on(‘tap’, function(e){

$(’#popupLayer’).hide();

$(’#bgMask’).hide();

$('#underLayer').css('pointer-events', 'none');

setTimeout(function(){
    $('#underLayer').css('pointer-events', 'auto');
}, 400);
           

});

3. fastclick

使用fastclick庫,其實作思路是,取消 click 事件(參看源碼 164-173 行),用 touchend 模拟快速點選行為(參看源碼 521-610 行)。

FastClick.attach(document.body);

從此所有點選事件都使用click,不會出現“穿透”的問題,并且沒有300ms的延遲。解決穿透的demo

有人(葉小钗)對事件機制做了詳細的剖析,循循善誘,并剖析了fastclick的源碼以自己模拟事件的建立。請看這篇文章,看完後一定會對移動端的事件有更深的了解