一。click 與 300ms 延遲
移動瀏覽器提供一個特殊的功能:雙擊(double tap)放大
300ms 的延遲就來自這裡,用戶碰觸頁面之後,需要等待一段時間來判斷是不是雙擊(double tap)動作,而不是立即響應單擊(click),等待的這段時間大約是 300ms。之前有過簡單介紹:[黯羽輕揚:HTML5 觸摸事件](/articles/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
寫了不少測試頁面,請查看:Git @OSC ayqy / my.js
參考資料
- 前輩博文若干
暫無評論,快來發表你的看法吧