天天看點

移動端頁面點選穿透問題

一、click與300ms延遲

    移動端浏覽器提供了一個特殊的功能:輕按兩下放大

300ms的延遲就是來自于這裡,使用者碰觸頁面之後,需要等待一段時間來判斷是不是輕按兩下(double tap)動作,而不是立即響應單擊(click),等待的這段時間大約是300ms。

之前有過簡單介紹(

黯羽輕揚:HTML5觸摸事件

移動事件提供了touchstart、touchmove、touchend卻沒有提供tap支援,主流架構(庫)都是手動實作了自定義tap事件,以消除300ms延遲,提高頁面響應速度,對于簡單的頁面,可以把touchstart或者touchend當做tap來用,但是存在一些問題,比如手直接觸目标元素,按住不放,慢慢移除響應區域,會觸發touchstart事件執行對應的事件處理器(本不應該觸發),touchend事件也存在類似的問題。

此外,使用原生touch事件也存在點選穿透的問題,因為click是在touch系列事件發生大約300ms才觸發的,混用touch和click肯定會導緻點透問題,下面詳細介紹

二、點選穿透問題

點選穿透現象有三種

    *點選穿透問題:點選蒙層(mask)上的關閉按鈕,蒙層消失後發現觸發了按鈕下面元素的click事件

蒙層關閉按鈕綁定的是touch事件,而按鈕下邊元素綁定的是click事件,touch事件觸發之後,蒙層消失了,300ms後這個點的click事件fire,event的target自然就是按鈕下面的元素,因為按鈕跟蒙層一起消息了。

    *跨頁面點選穿透事件:如果按鈕下面恰好是一個href屬性的a标簽,那麼頁面就會法神跳轉,因為a标簽跳轉預設是click事件觸發,是以原理和上面的完全相同

    *另一種跨頁面點選穿透問題:這次沒有mask了,直接點選頁内按鈕跳轉至新頁,然後發現新頁面中對應位置元素的click事件被觸發

和蒙層的道理一樣,js控制頁面跳轉的邏輯如果是綁定在touch事件上的,而且新頁面中對應位置的元素綁定的是click事件,而且頁面在300ms内完成了跳轉,三個條件同時滿足,就出現這種情況了

非要細分的話還有第四種,不過機率很低,就是新頁面中對應位置元素恰好是a标簽,然後就發生連續跳轉了。。。諸如此類的,都是點選穿透問題

三、為什麼會出現點透

click延遲、延遲、還是延遲

在移動端不使用click而用touch事件代替觸摸是因為click事件有着明顯的延遲,具體touchstart與click的差別如下:

    *touchstart:在這個DOM(或者冒泡到這個DOM)上手指觸摸開始即能立即出發

    *click:在這個DOM(或者冒泡這個DOM)上手指觸摸開始,且手指未在螢幕上移動(某些<a href=”http://www.it165.net/edu/ewl/” target=”_blank” class=”keylink”>浏覽器</a>允許移動一個非常小的位移值),且在這個DOM元素上手指離開螢幕,且觸摸和離開螢幕之間的間隔時間較短(某些浏覽器不檢測間隔時間,也會觸發click)才能觸發

也就是說,事件的觸發事件按照由早到晚排列為:touchstart早于touchend早于click。亦即click的觸發是有延遲的,這個時間大概在300ms左右(即使給元素綁定的是touch事件,touchstart=>touchend=>click,click事件依然會被觸發)

由于我們在touchstart階段就已經隐藏了罩層A,當click被觸發的時候,能夠被點選的元素則是罩層下面的B元素綁定的事件,根據click事件的觸發規則:

隻有在被觸發的時候,目前有click事件的元素顯示,且在面朝使用者的最前端時,才出發click事件。

由于B綁定了click事件(或者B本身預設存在click事件),是以B的click事件被觸發,産生了點透的情況。

解決方案

對于B元素本身沒有預設click事件的情況(無a标簽等),應統一使用touch事件,統一代碼風格,并且由于click事件在移動端的延遲要大很多,不利于使用者體驗,是以關于觸摸事件應盡量使用touch相關事件。

對于B元素本身存在預設click事件的情況,應及時取消A元素的預設點選事件,進而阻止click事件的産生。即應在上例的handle函數中添加代碼如下:

移動端頁面點選穿透問題

對于遮蓋浮層,由于遮蓋浮層的點選即使有小延遲也是沒有關系的,反而會有疑似更好的使用者體驗,是以這種情況,可以針對遮蓋浮層自己采用click事件,這樣就不會出現點透問題。

四、解決方案

問題已經很明了了,有很多解決方案,但是思路不外乎2種:

1、不要混用touch和click

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

2、吃掉或者消費掉touch之後的click

        依舊用tap,隻是在可能發生點選穿透的情形做額外的處理,拿個東西來擋住或者tap後延遲350ms在隐藏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

2、隻是用click

下下策,因為帶來300ms延遲,頁面内任何一個滴定儀監護都将增加300ms延遲,想想都慢

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

3、拿個東西來擋住

比較笨的方法,不推薦用

4、tap後延遲350ms在隐藏mask

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

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

5、pointer-events

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

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

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

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

6、比較麻煩, 不建議使用

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

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

7、fastclick

好用的解決方案,不介意多加載幾kb的話,不建議使用,因為有人遇到了bug,更多資訊請檢視:

Fastclick 導緻click事件觸發兩次的問題

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

參考文檔:

http://www.uedsc.com/through-the-click-point-in-the-development-of-web.html

繼續閱讀