一、前言
移動端瀏覽器提供一個特殊的功能:雙擊(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
標籤,那瀏覽器就可以認為該網站已經對移動端做過了適配和優化,就無需雙擊縮放操作了。
這個方案相比方案一的好處在於,它沒有完全禁用縮放,而只是禁用了瀏覽器預設的雙擊縮放行為,但使用者仍然可以通過雙指縮放操作來縮放頁面。
除了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
FastClick是FT 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),關注我們每天閱讀更多精彩內容】