跳到主要內容
黯羽輕揚每天積累一點點

移動頁面點擊穿透問題解決方案

免費2015-08-24#JS#Front-End#Solution#click through#zepto点击穿透#点透#点穿#300ms延迟#解决点透问题#跨页面点击穿透#ghost click

移動頁面的點擊穿透問題是很容易遇到的初級問題,本文詳細解釋其原理以及各種解決方案

一。click 與 300ms 延遲

移動瀏覽器提供一個特殊的功能:雙擊(double tap)放大

300ms 的延遲就來自這裡,用戶碰觸頁面之後,需要等待一段時間來判斷是不是雙擊(double tap)動作,而不是立即響應單擊(click),等待的這段時間大約是 300ms。之前有過簡單介紹:[黯羽輕揚:HTML5 觸摸事件](/articles/html5 觸摸事件/)

移動事件提供了 touchstarttouchmovetouchend 卻沒有提供 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 種:

  1. 不要混用 touch 和 click

既然 touch 之後 300ms 會觸發 click,只用 touch 或者只用 click 就自然不會存在問題了

  1. 吃掉(或者說是消費掉)touch 之後的 click

依舊用 tap,只是在可能發生點擊穿透的情形做額外的處理,拿個東西來擋住、或者 tap 後延遲 350 毫秒再隱藏 mask、pointer-events、在下面元素的事件處理器裡做檢測(配合全局 flag)等等,能吃掉就行

詳細解決方案:

  1. 只用 touch

最簡單的解決方案,完美解決點擊穿透問題

把頁面內所有 click 全部換成 touch 事件(touchstart、'touchend'、'tap'),需要特別注意a 標籤,a 標籤的 href 也是 click,需要去掉換成 js 控制的跳轉,或者直接改成 span + tap 控制跳轉。如果要求不高,不在乎滑走或者滑進來觸發事件的話,span + touchend 就可以了,畢竟 tap 需要引入第三方庫

不用 a 標籤其實沒什麼,移動 app 開發不用考慮 SEO,即便用了 a 標籤,一般也會去掉所有預設樣式,不如直接用 span

  1. 只用 click

下下策,因為會帶來 300ms 延遲,頁面內任何一個自定義交互都將增加 300 毫秒延遲,想想都慢

不用 touch 就不會存在 touch 之後 300ms 觸發 click 的問題,如果交互性要求不高可以這麼做,強烈不推薦,快一點總是好的

  1. 拿個東西來擋住

比較笨的方法,千萬不要用

葉小釵的「菊花」大法,更多信息請查看 【移動端兼容問題研究】javascript 事件機制詳解(涉及移動兼容)

  1. tap 後延遲 350ms 再隱藏 mask

改動最小,缺點是隱藏 mask 變慢了,350ms 還是能感覺到慢的

只需要針對 mask 做處理就行,改動非常小,如果要求不高的話,用這個比較省力

  1. pointer-events

比較麻煩且有缺陷,不建議使用

mask 隱藏後,給按鈕下面元素添上 pointer-events: none; 樣式,讓 click 穿過去,350ms 後去掉這個樣式,恢復響應

缺陷是 mask 消失後的的 350ms 內,用戶可以看到按鈕下面的元素點著沒反應,如果用戶手速很快的話一定會發現

  1. 在下面元素的事件處理器裡做檢測(配合全局 flag)

比較麻煩,不建議使用

全局 flag 記錄按鈕點擊的位置(坐標點),在下面元素的事件處理器裡判斷 event 的坐標點,如果相同則是那個可惡的 click,拒絕響應

上面說的只是想法,沒測試過,實在不行就用記錄時間戳判斷,等待 350ms,這樣就和 pointer-events 差不多

  1. fastclick

好用的解決方案,不介意多加載幾 KB 的話,不建議使用,因為有人遇到了 bug,更多信息請查看:Fastclick 導致 click 事件觸發兩次的問題

首先引入 fastclick 庫,再把頁面內所有 touch 事件都換成 click,其實稍微有點麻煩,建議引入這幾 KB 就為了解決點透問題不值得,不如用第一種方法呢

四。DEMO

寫了不少測試頁面,請查看:Git @OSC ayqy / my.js

參考資料

  • 前輩博文若干

評論

暫無評論,快來發表你的看法吧

提交評論