設計方案--移動端延遲300ms的原因以及解決方案

saucxs發表於2019-06-21

一、前言

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

 

二、移動端延遲300ms的原因

為什麼要用觸控事件?觸控事件是移動端瀏覽器特有的html5事件。

因為移動端的click有很大延遲(大約300ms),300ms延遲來自判斷雙擊和長按,因為只有預設等待時間結束以確定沒有後續動作發生時,才會觸發click事件。而觸控事件的延遲則是非常短的,使用觸控事件的能夠提高頁面響應速度,帶來更好的使用者體驗。

重點:由於移動端會有雙擊縮放的這個操作,因此瀏覽器在click之後要等待300ms,看使用者有沒有下一次點選,也就是這次操作是不是雙擊。

 

三、瀏覽器開發商的解決方案

1、方案一:禁用縮放

當HTML文件頭部包含如下meta標籤時:

<meta name="viewport" content="user-scalable=no">
<meta name="viewport" content="initial-scale=1,maximum-scale=1">

表明這個頁面是不可縮放的,那雙擊縮放的功能就沒有意義了,此時瀏覽器可以禁用預設的雙擊縮放行為並且去掉300ms的點選延遲

缺點:就是必須通過完全禁用縮放來達到去掉點選延遲的目的,然而完全禁用縮放並不是我們的初衷,我們只是想禁掉預設的雙擊縮放行為,這樣就不用等待300ms來判斷當前操作是否是雙擊。但是通常情況下,我們還是希望頁面能通過雙指縮放來進行縮放操作,比如放大一張圖片,放大一段很小的文字。

 

2、方案二:更改預設的視口視窗

為了讓桌面站點能在移動端瀏覽器正常顯示,移動端瀏覽器預設的視口寬度!=裝置瀏覽器視窗寬度,而是視口寬度要比裝置寬度大,通常是980px。

我們可以通過以下標籤來設定視口寬度裝置寬度

<meta name="viewport" content="width=device-width">
 

對移動端坐過適配和優化了,這個時候就不需要雙擊縮放了。如果能夠識別出一個網站是響應式的網站,那麼移動端瀏覽器就可以自動禁掉預設的雙擊縮放行為並且去掉300ms的點選延遲。如果設定了上述meta標籤,那瀏覽器就可以認為該網站已經對移動端做過了適配和優化,就無需雙擊縮放操作了。

這個方案相比方案一的好處在於,它沒有完全禁用縮放,而只是禁用了瀏覽器預設的雙擊縮放行為,但使用者仍然可以通過雙指縮放操作來縮放頁面

 

方案三:css 的 touch-action

除了IE之外的大部分瀏覽器都不支援這個新的CSS屬性。touch-action這個CSS屬性。這個屬性指定了相應元素上能夠觸發的使用者代理(也就是瀏覽器)的預設行為。如果將該屬性值設定為touch-action: none,那麼表示在該元素上的操作不會觸發使用者代理的任何預設行為,就無需進行300ms的延遲判斷

 

四、程式碼解決方案

1、方案一:指標事件polyfill

除了IE,其他大部分瀏覽器都還不支援指標事件。有一些JS庫,可以讓我們提前使用指標事件。比如:

(1)谷歌的Polymer

(2)微軟的HandJS

(3)@Rich-Harris 的 Points

關心的不是指標事件,而是與300ms延遲相關的CSS屬性touch-action。由於除了IE之外的大部分瀏覽器都不支援這個新的CSS屬性,所以這些指標事件的polyfill必須通過某種方式去模擬支援這個屬性。一種方案是JS去請求解析所有的樣式表,另一種方案是將touch-action作為html標籤的屬性。

 

2、方案二:FastClick

FastClickFT Labs專門為解決移動端瀏覽器 300 毫秒點選延遲問題所開發的一個輕量級的庫。FastClick的實現原理是在檢測到touchend事件的時候,會通過DOM自定義事件立即出發模擬一個click事件,並把瀏覽器在300ms之後的click事件阻止掉。

 

五、點選穿透問題

說完移動端點選300ms延遲的問題,還不得不提一下移動端點選穿透的問題。既然click點選有300ms的延遲,那對於觸控式螢幕,我們直接監聽touchstart事件不就好了嗎?

使用touchstart去代替click事件有兩個不好的地方。

第一:touchstart是手指觸控螢幕就觸發,有時候使用者只是想滑動螢幕,卻觸發了touchstart事件,這不是我們想要的結果;

第二:使用touchstart事件在某些場景下可能會出現點選穿透的現象。

1、什麼是點選穿透?

假如頁面上有兩個元素A和B。B元素在A元素之上。我們在B元素的touchstart事件上註冊了一個回撥函式,該回撥函式的作用是隱藏B元素。我們發現,當我們點選B元素,B元素被隱藏了,隨後,A元素觸發了click事件。

這是因為在移動端瀏覽器事件執行的順序是touchstart > touchend > click。而click事件有300ms的延遲,當touchstart事件把B元素隱藏之後,隔了300ms,瀏覽器觸發了click事件,但是此時B元素不見了,所以該事件被派發到了A元素身上。如果A元素是一個連結,那此時頁面就會意外地跳轉。

 

2、點選穿透現象3種情況

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

(2)跨頁面點選穿透問題:如果按鈕下面恰好是一個有href屬性的a標籤,那麼頁面就會發生跳轉因為 a標籤跳轉預設是click事件觸發 ,所以原理和上面的完全相同

(3)點選穿透問題:這次沒有mask了,直接點選頁內按鈕跳轉至新頁,然後發現新頁面中對應位置元素的click事件被觸發了。

 

3、解決方案

2種思路:

(1)不要混用touch和click。既然touch之後300ms會觸發click,只用touch或者只用click就自然不會存在問題了。

(2)用掉(或者說是消費掉)touch之後的click。依舊用tap,只是在可能發生點選穿透的情形做額外的處理,拿個東西來擋住、或者tap後延遲350毫秒再隱藏mask、pointer-events、在下面元素的事件處理器裡做檢測(配合全域性flag)

詳細方案:

(1)只用touch

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

把頁面內所有click全部換成touch事件 touchstart 、’touchend’、’tap’, 需要特別注意 a標籤,a標籤的href也是click,需要去掉換成js控制的跳轉,或者直接改成span + tap控制跳轉。

(2)只用click

下下策 ,因為會帶來300ms延遲,頁面內任何一個自定義互動都將增加300毫秒延遲,想想都慢。不用touch就不會存在touch之後300ms觸發click的問題。

(3)拿個東西擋住

比較笨的方法, 千萬不要用。更多資訊請檢視 【移動端相容問題研究】javascript事件機制詳解(涉及移動相容)

(4)tap後延遲350ms再隱藏mask

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

(5)pointer-events

比較麻煩且有缺陷, 不建議使用。mask隱藏後,給按鈕下面元素添上 pointer-events: none; 樣式,讓click穿過去,350ms後去掉這個樣式,恢復響應。缺陷是mask消失後的的350ms內,使用者可以看到按鈕下面的元素點著沒反應,如果使用者手速很快的話一定會發現。

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

比較麻煩, 不建議使用。全域性flag記錄按鈕點選的位置(座標點),在下面元素的事件處理器裡判斷event的座標點,如果相同則是那個可惡的click,拒絕響應。

(7)fastclick

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

 

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

 

六、瀏覽器事件觸發的順序

touchstart --> mouseover(有的瀏覽器沒有實現) --> mousemove(一次) -->mousedown --> mouseup --> click -->touchend

Touch 事件中,常用的為 touchstart, touchmove, touchend 三種。除此之外還有touchcancel。 注意,原生事件中並沒有tap事件。

事件描述如下:

事件描述觸發時機
touchstart 開始觸控 手指接觸螢幕時立即觸發
touchmove 移動或拖拽 取決於系統和瀏覽器
touchend 觸控結束 手指離開螢幕時立即出發

而Touch事件的觸發一般通過手指,還會存在多點觸控,拖拽方向等情況。列出幾個重要引數如下:

引數含義
touches 螢幕中每根手指資訊列表
targetTouches 和touches類似,把同一節點的手指資訊過濾掉
changedTouches 響應當前事件的每根手指的資訊列表

程式碼獲取如下:

elemenrRef.addEventListener('touchstart', function(e) {   
    console.log(e.touches, e.targetTouches, e.changedTouches);}
);

 

手指觸發觸控事件的過程如下:

touchstart --> mouseover(有的瀏覽器沒有實現) --> mousemove(一次) -->mousedown --> mouseup --> click -->touchend

由此,我們可以在 ontouchstart 事件上記錄開始觸控開始,ontouchend 記錄觸控結束資訊。 通過上述這些引數,很容易的去計算幽冥點選的時間,以及點選穿透的相關資訊,包括響應的座標情況

 

【注:我是saucxs,也叫songEagle,鬆寶寫程式碼,文章首發於sau交流學習社群 https://www.mwcxs.top),關注我們每天閱讀更多精彩內容】

相關文章