移動端h5頁面touch事件與點擊穿透問題

前言

作過移動端H5頁面的同窗確定知道,移動端web的事件模型不一樣於PC頁面的事件。看了一些關於touch事件的文章,我想再來回顧下touch事件的原理,爲何經過touch能夠觸發click事件,touch事件是否是萬能的以及它可能存在的問題。javascript

touch事件的來源

PC網頁上的大部分操做都是用鼠標的,即響應的是鼠標事件,包括mousedownmouseupmousemoveclick事件。一次點擊行爲,可被拆解成:mousedown -> click -> mouseup三步。css

手機上沒有鼠標,因此就用觸摸事件去實現相似的功能。touch事件包含touchstarttouchmovetouchend,注意手機上並無tap事件。手指觸發觸摸事件的過程爲:touchstart -> touchmove -> touchendhtml

手機上沒有鼠標,但不表明手機不能響應mouse事件(實際上是藉助touch去觸發mouse事件)。有人在PC和手機上對事件作了對比實驗,以說明手機對touch事件相應速度快於mouse事件。java

能夠看到在手機上,當咱們手觸碰屏幕時,要過300ms左右纔會觸發mousedown事件,因此click事件在手機上看起來就像慢半拍同樣。git

touch事件中能夠獲取如下參數

參數 含義
touches 屏幕中每根手指信息列表
targetTouches 和touches相似,把同一節點的手指信息過濾掉
changedTouches 響應當前事件的每根手指的信息列表

tap是怎麼來的

用過Zepto或KISSY等移動端js庫的人確定對tap事件不陌生,咱們作PC頁面時綁定click,相應地手機頁面就綁定tap。但原生的touch事件自己是沒有tap的,js庫裏提供的tap事件都是模擬出來的。github

咱們在上面看到,手機上響應 click 事件會有300ms的延遲,那麼這300ms究竟是幹嗎了?瀏覽器在 touchend 後會等待約300ms,緣由是判斷用戶是否有雙擊(double tap)行爲。若是沒有 tap 行爲,則觸發 click 事件,而雙擊過程當中就不適合觸發 click 事件了。由此能夠看出 click 事件觸發表明一輪觸摸事件的結束。web

既然說tap事件是模擬出來的,咱們能夠看下Zepto對 singleTap 事件的處理。見源碼 136-143 行,能夠看出在 touchend 響應 250ms 無操做後,則觸發singleTap。chrome

點擊穿透的場景

有了以上的基礎,咱們就能夠理解爲何會出現點擊穿透現象了。咱們常常會看到「彈窗/浮層」這種東西,我作個了個demo。瀏覽器

整個容器裏有一個底層元素的div,和一個彈出層div,爲了讓彈出層有模態框的效果,我又加了一個遮罩層。移動端web

<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「穿透」到下層去了。

完整demo請用chrome手機模擬器查看,或直接掃描二維碼在手機上查看。

結合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中的屬性,它有不少取值,有用的主要是autonone,其餘屬性值爲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的源碼以本身模擬事件的建立。請看這篇文章,看完後必定會對移動端的事件有更深的瞭解

相關文章
相關標籤/搜索