移動端300ms點選延遲和點選穿透問題

發表於2016-05-14

一、移動端300ms點選延遲

一般情況下,如果沒有經過特殊處理,移動端瀏覽器在派發點選事件的時候,通常會出現300ms左右的延遲。也就是說,當我們點選頁面的時候移動端瀏覽器並不是立即作出反應,而是會等上一小會兒才會出現點選的效果。在移動WEB興起的初期,使用者對300ms的延遲感覺不明顯。但是,隨著使用者對互動體驗的要求越來越高,現今,移動端300ms的點選延遲逐漸變得明顯而無法忍受。

那麼,移動端300ms的點選延遲是怎麼來的呢?

問題由來
這要追溯至 2007 年初。蘋果公司在釋出首款 iPhone 前夕,遇到一個問題:當時的網站都是為大螢幕裝置所設計的。於是蘋果的工程師們做了一些約定,應對 iPhone 這種小螢幕瀏覽桌面端站點的問題。

這當中最出名的,當屬雙擊縮放(double tap to zoom),這也是會有上述 300 毫秒延遲的主要原因。

雙擊縮放,顧名思義,即用手指在螢幕上快速點選兩次,iOS 自帶的 Safari 瀏覽器會將網頁縮放至原始比例。 那麼這和 300 毫秒延遲有什麼聯絡呢? 假定這麼一個場景。使用者在 iOS Safari 裡邊點選了一個連結。由於使用者可以進行雙擊縮放或者雙擊滾動的操作,當使用者一次點選螢幕之後,瀏覽器並不能立刻判斷使用者是確實要開啟這個連結,還是想要進行雙擊操作。因此,iOS Safari 就等待 300 毫秒,以判斷使用者是否再次點選了螢幕。 鑑於iPhone的成功,其他移動瀏覽器都複製了 iPhone Safari 瀏覽器的多數約定,包括雙擊縮放,幾乎現在所有的移動端瀏覽器都有這個功能。之前人們剛剛接觸移動端的頁面,在欣喜的時候往往不會care這個300ms的延時問題,可是如今touch端介面如雨後春筍,使用者對體驗的要求也更高,這300ms帶來的卡頓慢慢變得讓人難以接受。

也就是說,移動端瀏覽器會有一些預設的行為,比如雙擊縮放、雙擊滾動。這些行為,尤其是雙擊縮放,主要是為桌面網站在移動端的瀏覽體驗設計的。而在使用者對頁面進行操作的時候,移動端瀏覽器會優先判斷使用者是否要觸發預設的行為。

那有什麼辦法可以解決這個問題呢?

瀏覽器開發商的解決方案

瀏覽器開發商要對移動端瀏覽器本身的設計進行改善,以提供長遠的解決方案。

目前,瀏覽器開發商的解決方案主要有一下三種方案:
方案一:禁用縮放
當HTML文件頭部包含如下meta標籤時:

表明這個頁面是不可縮放的,那雙擊縮放的功能就沒有意義了,此時瀏覽器可以禁用預設的雙擊縮放行為並且去掉300ms的點選延遲。
這個方案有一個缺點,就是必須通過完全禁用縮放來達到去掉點選延遲的目的,然而完全禁用縮放並不是我們的初衷,我們只是想禁掉預設的雙擊縮放行為,這樣就不用等待300ms來判斷當前操作是否是雙擊。但是通常情況下,我們還是希望頁面能通過雙指縮放來進行縮放操作,比如放大一張圖片,放大一段很小的文字。

方案二:更改預設的視口寬度
一開始,為了讓桌面站點能在移動端瀏覽器正常顯示,移動端瀏覽器預設的視口寬度並不等於裝置瀏覽器視窗寬度,而是要比裝置瀏覽器視窗寬度大,通常是980px。我們可以通過以下標籤來設定視口寬度為裝置寬度。

因為雙擊縮放主要是用來改善桌面站點在移動端瀏覽體驗的,而隨著響應式設計的普及,很多站點都已經對移動端坐過適配和優化了,這個時候就不需要雙擊縮放了,如果能夠識別出一個網站是響應式的網站,那麼移動端瀏覽器就可以自動禁掉預設的雙擊縮放行為並且去掉300ms的點選延遲。如果設定了上述meta標籤,那瀏覽器就可以認為該網站已經對移動端做過了適配和優化,就無需雙擊縮放操作了。
這個方案相比方案一的好處在於,它沒有完全禁用縮放,而只是禁用了瀏覽器預設的雙擊縮放行為,但使用者仍然可以通過雙指縮放操作來縮放頁面。

方案三:CSS touch-action
網上很多文章把這個方案歸結為指標事件,這令我很疑惑。

以我的理解來看,指標事件的提出並不是為了解決300ms點選延遲的,而是為了使用一個單獨的事件模型,對滑鼠、觸控、觸控等多種輸入型別進行統一的處理。也就是說,移動瀏覽器不用再為不同的輸入裝置設計不同的事件,網頁的開發者也不用再為不同輸入型別的裝置寫不同的事件響應程式碼,而是通過統一的指標事件就可以開發出跨不同輸入型別終端的應用。

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

而設定這個CSS屬性與否,指標事件應該都是可以工作的。所以,網上的文章令我很疑惑,希望有大神能給我指示~ 。。~

現有的解決方案

要解決300ms點選延遲的問題,從長遠來說,自然還是得瀏覽器開發商提供統一的最終的解決方案。但是,到目前為止,以上三種方案並不能提供很好的相容性,對於方案一和方案二,Chrome是率先支援的,Firefox緊隨其後,然而令Safari頭疼的是,它除了雙擊縮放還有雙擊滾動操作,如果採用這種兩種方案,那勢必連雙擊滾動也要一起禁用;對於方案三,IE是支援的,但是其他瀏覽器支援不完善。具體請看這篇文章:移動端Click300毫秒點選延遲的來龍去脈(轉)

所以,在瀏覽器開發商最終統一的解決方案出來之前,我們還有一些基於Javascript的現成的解決方案可以用。

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

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

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

二、點選穿透問題

說完移動端點選300ms延遲的問題,還不得不提一下移動端點選穿透的問題。可能有人會想,既然click點選有300ms的延遲,那對於觸控式螢幕,我們直接監聽touchstart事件不就好了嗎?
使用touchstart去代替click事件有兩個不好的地方。
第一:touchstart是手指觸控螢幕就觸發,有時候使用者只是想滑動螢幕,卻觸發了touchstart事件,這不是我們想要的結果;
第二:使用touchstart事件在某些場景下可能會出現點選穿透的現象。

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

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

相關文章