作過移動端H5頁面的同窗確定知道,移動端web的事件模型不一樣於PC頁面的事件。看了一些關於touch事件的文章,我想再來回顧下touch事件的原理,爲何經過touch能夠觸發click事件,touch事件是否是萬能的以及它可能存在的問題。javascript
PC網頁上的大部分操做都是用鼠標的,即響應的是鼠標事件,包括mousedown
、mouseup
、mousemove
和click
事件。一次點擊行爲,可被拆解成:mousedown
-> click
-> mouseup
三步。css
手機上沒有鼠標,因此就用觸摸事件去實現相似的功能。touch事件包含touchstart
、touchmove
、touchend
,注意手機上並無tap
事件。手指觸發觸摸事件的過程爲:touchstart
-> touchmove
-> touchend
。html
手機上沒有鼠標,但不表明手機不能響應mouse事件(實際上是藉助touch去觸發mouse事件)。有人在PC和手機上對事件作了對比實驗,以說明手機對touch事件相應速度快於mouse事件。java
能夠看到在手機上,當咱們手觸碰屏幕時,要過300ms左右纔會觸發mousedown
事件,因此click
事件在手機上看起來就像慢半拍同樣。git
參數 | 含義 |
---|---|
touches | 屏幕中每根手指信息列表 |
targetTouches | 和touches相似,把同一節點的手指信息過濾掉 |
changedTouches | 響應當前事件的每根手指的信息列表 |
用過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中的 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 而彈出輸入鍵盤,也就出現了上面的「點透」現象。
因爲 click 事件的滯後性,在這段時間內原來點擊的元素消失了,因而便「穿透」了。所以咱們順着這個思路就想到,能夠給元素的消失作一個fade效果,相似jQuery裏的fadeOut
,並設置動畫duration大於300ms,這樣當延遲的 click 觸發時,就不會「穿透」到下方的元素了。
一樣的道理,不用延時動畫,咱們還能夠動態地在觸摸位置生成一個透明的元素,這樣當上層元素消失而延遲的click來到時,它點擊到的是那個透明的元素,也不會「穿透」到底下。在必定的timeout後再將生成的透明元素移除。具體可見demo
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); });
使用fastclick庫,其實現思路是,取消 click 事件(參看源碼 164-173 行),用 touchend 模擬快速點擊行爲(參看源碼 521-610 行)。
FastClick.attach(document.body);
今後全部點擊事件都使用click
,不會出現「穿透」的問題,而且沒有300ms的延遲。解決穿透的demo
有人(葉小釵)對事件機制作了詳細的剖析,循循善誘,並剖析了fastclick的源碼以本身模擬事件的建立。請看這篇文章,看完後必定會對移動端的事件有更深的瞭解