一.click與300ms延遲
移動浏覽器提供一個特殊的功能:輕按兩下(double tap)放大
300ms的延遲就來自這裡,使用者碰觸頁面之後,需要等待一段時間來判斷是不是輕按兩下(double tap)動作,而不是立即響應單擊(click),等待的這段時間大約是300ms。之前有過簡單介紹:黯羽輕揚:HTML5觸摸事件
移動事件提供了
touchstart
、
touchmove
、
touchend
卻沒有提供tap支援,主流架構(庫)都是手動實作了自定義tap事件,以求消除300ms延遲,提高頁面響應速度。對于簡單的頁面,可以把
touchstart
或者
touchend
當作tap來用,但存在一些問題,比如手指接觸目标元素,按住不放,慢慢移出響應區域,會觸發
touchstart
事件執行對應的事件處理器(本不應該觸發),
touchend
事件也存在類似的問題。
此外,使用原生touch事件也存在點選穿透的問題,因為click是在touch系列事件發生後大約300ms才觸發的,混用touch和click肯定會導緻點透問題,下面詳細介紹
二.點選穿透問題
點選穿透現象有3種:
-
點選穿透問題:點選蒙層(mask)上的關閉按鈕,蒙層消失後發現觸發了按鈕下面元素的click事件
蒙層的關閉按鈕綁定的是touch事件,而按鈕下面元素綁定的是click事件,touch事件觸發之後,蒙層消失了,300ms後這個點的click事件fire,event的target自然就是按鈕下面的元素,因為按鈕跟蒙層一起消失了
-
跨頁面點選穿透問題:如果按鈕下面恰好是一個有href屬性的a标簽,那麼頁面就會發生跳轉
因為a标簽跳轉預設是click事件觸發,是以原理和上面的完全相同
-
另一種跨頁面點選穿透問題:這次沒有mask了,直接點選頁内按鈕跳轉至新頁,然後發現新頁面中對應位置元素的click事件被觸發了
和蒙層的道理一樣,js控制頁面跳轉的邏輯如果是綁定在touch事件上的,而且新頁面中對應位置的元素綁定的是click事件,而且頁面在300ms内完成了跳轉,三個條件同時滿足,就出現這種情況了
非要細分的話還有第四種,不過機率很低,就是新頁面中對應位置元素恰好是a标簽,然後就發生連續跳轉了。。。諸如此類的,都是點選穿透問題
三.解決方案
問題已經很明了了,有很多解決方案,但思路不外乎2種:
-
不要混用touch和click
既然touch之後300ms會觸發click,隻用touch或者隻用click就自然不會存在問題了
-
吃掉(或者說是消費掉)touch之後的click
依舊用tap,隻是在可能發生點選穿透的情形做額外的處理,拿個東西來擋住、或者tap後延遲350毫秒再隐藏mask、pointer-events、在下面元素的事件處理器裡做檢測(配合全局flag)等等,能吃掉就行
詳細解決方案:
-
隻用touch
最簡單的解決方案,完美解決點選穿透問題
把頁面内所有click全部換成touch事件(
touchstart
、’touchend’、’tap’),需要特别注意a标簽,a标簽的href也是click,需要去掉換成js控制的跳轉,或者直接改成span + tap控制跳轉。如果要求不高,不在乎滑走或者滑進來觸發事件的話,span + touchend就可以了,畢竟tap需要引入第三方庫
不用a标簽其實沒什麼,移動app開發不用考慮SEO,即便用了a标簽,一般也會去掉所有預設樣式,不如直接用span
-
隻用click
下下策,因為會帶來300ms延遲,頁面内任何一個自定義互動都将增加300毫秒延遲,想想都慢
不用touch就不會存在touch之後300ms觸發click的問題,如果互動性要求不高可以這麼做,強烈不推薦,快一點總是好的
-
拿個東西來擋住
比較笨的方法,千萬不要用
葉小钗的“菊花”大法,更多資訊請檢視【移動端相容問題研究】javascript事件機制詳解(涉及移動相容)
-
tap後延遲350ms再隐藏mask
改動最小,缺點是隐藏mask變慢了,350ms還是能感覺到慢的
隻需要針對mask做處理就行,改動非常小,如果要求不高的話,用這個比較省力
-
pointer-events
比較麻煩且有缺陷,不建議使用
mask隐藏後,給按鈕下面元素添上
pointer-events: none;
樣式,讓click穿過去,350ms後去掉這個樣式,恢複響應
缺陷是mask消失後的的350ms内,使用者可以看到按鈕下面的元素點着沒反應,如果使用者手速很快的話一定會發現
-
在下面元素的事件處理器裡做檢測(配合全局flag)
比較麻煩,不建議使用
全局flag記錄按鈕點選的位置(坐标點),在下面元素的事件處理器裡判斷event的坐标點,如果相同則是那個可惡的click,拒絕響應
上面說的隻是想法,沒測試過,實在不行就用記錄時間戳判斷,等待350ms,這樣就和
差不多pointer-events
-
fastclick
好用的解決方案,不介意多加載幾KB的話,不建議使用,因為有人遇到了bug,更多資訊請檢視:Fastclick 導緻click事件觸發兩次的問題
首先引入fastclick庫,再把頁面内所有touch事件都換成click,其實稍微有點麻煩,建議引入這幾KB就為了解決點透問題不值得,不如用第一種方法呢
四.DEMO
寫了不少測試頁面,請檢視:[email protected] ayqy / my.js
參考資料
- 前輩博文若幹