移動端 click 事件 300ms 延遲的前世今生

原文首發于 baishusama.github.io,歡迎圍觀~

存疑

最開始,我遇到的其實是“移動端遮罩層滑動穿透”的問題。

在查找“滑動穿透”問題相關資料的時候,我搜到了很多 click 300ms 延遲的問題。我那個時候有些不知所云,因為我自己并沒有真實遇到過 300ms 延遲現象,也就沒怎么在意。

時至今日想動筆總結遇到了若干次的“滑動穿透”問題的時候,搜集資料的偶然間得以解惑 300ms 的前世今生。

移動端 click 的 300ms 延遲

那么,這 300ms 延遲到底是從哪里來的呢?

時間要追溯到 2007 年初代 iPhone 發布前夕,蘋果為了解決“如何用手機這種小尺寸屏幕來顯示 PC 端網頁”這個問題,提出了很多聰明的約定(convention)。而后因為 iPhone 的大獲成功,這些約定被各大手機瀏覽器爭相效仿。

這些約定之中,雙擊縮放(Double Tap to Zoom) 就是 300ms 的“元兇”——當用戶在頁面上 click 的時候,瀏覽器為了判斷這個用戶操作是單擊還是雙擊,會等待 300-350ms 。如果 300ms 內,發生了第二次 click 事件,那么視為雙擊;否則為單擊,等 300ms 時間過去之后,才觸發 click 事件。

在那個還不存在響應式設計和雙指縮放(Pinch to Zoom)的時代,這個延遲是一個合理的預防措施。但不幸的是,這 300ms 的延遲已經成為用戶覺得 web 應用比 native 應用更慢、性能不及后者的主要原因之一。諸如,鏈接、按鈕、多選框等基于 click 交互的元素,以及 JS 對 click 事件的監聽,都因此受到影響。

幸運的是,瀏覽器開發商(vendor)和開發者都注意到了這個問題,提出了一些解決方案。

解決方案

方案一、?? 禁用縮放

  • 代碼:
    <meta name="viewport" content="user-scalable=no">
    <!-- 或者 -->
    <meta name="viewport" content="initial-scale=1, minimum-scale=1, maximum-scale=1">
    
  • 原理:雙擊是為了縮放,如果禁用縮放,那么就沒雙擊什么事兒了,也不需要額外等待 300ms 了。
  • 支持情況:在 Android 平臺上,由 Chrome 最先提出,FireFox、Opera 等瀏覽器也相繼支持;IOS 9.3 開始一度支持,IOS10 開始不再支持。
  • 缺點:Safari 不支持。而且,禁用縮放會損害移動端網頁的可用性和可訪問性。例如,可能無法放大網頁中的一張圖片或一段字體較小的文字。

這里要注意區分:“雙擊縮放”(Double Tap to Zoom)和“雙指縮放”(Pinch to Zoom)。為了兼顧消除 300ms 延遲和不損害可用性和可訪問性,我們應該拋棄雙擊縮放、擁抱雙指縮放。

方案二、?? 視窗寬度設置為設備寬度

  • 代碼:
    <meta name="viewport" content="width=device-width">
    
  • 原由:正如 Chromium Code Reviews 上說的,viewportwidth 設置得小于等于 device-width 的頁面,是針對移動端優化過的或者是響應式的站點,其內容足夠清晰,雙擊縮放失去了意義。因此,為包含上面這行代碼的頁面禁用雙擊縮放。同時,雙指縮放得以保留,從而也就沒有可用性和可訪問性問題了。
  • 支持情況:自 Chrome 32 開始,FF、IE/Edge 也隨后支持了;2016 年 3 月,IOS 9.3 開始支持。
  • 推薦使用!

該解決方案的“禁止雙擊縮放”是遵守如下規則的:

  • 當頁面設置了視窗寬度為設備寬度且是初始尺寸(頁面尚未縮放),此時,雙擊縮放才是被禁止的。
  • 如果視窗尺寸不是初始尺寸(頁面已經縮放),雙擊縮放是被允許的。
  • 為了在用戶結束縮放后仍能 fast-click ,縮小時,只能縮小到初始尺寸,而不是最小尺寸。

方案三、?? 指針事件(Pointer Events)

  • 代碼:
    a, button, .myelements {
        -ms-touch-action: manipulation; /* IE10  */
        touch-action: manipulation;     /* IE11+ */
    }
    
  • 根據規范:CSS 屬性 touch-action 決定了觸摸輸入(touch input)能否觸發 UA (User Agent)支持的默認行為。這包括但不限于諸如平移或縮放等行為。
  • 根據 MDNtouch-actionmanipulation 值激活了平移和雙指縮放手勢,而禁用了雙擊縮放等非標準的手勢。
  • 支持情況:在 Can I Use 上可以看出,除了 Opera Mini 不支持、FF 需要手動啟用和 Android 4.x 的自帶瀏覽器有些迷之外,其他瀏覽器支持良好。
  • 推薦使用!

在只有 IE 支持指針事件的初期,誕生了不少指針事件的 polyfill 解決方案。在仍不支持指針事件的瀏覽器上,這是一種變通的方式。

shim VS polyfill

  1. 一個 shim 是一個庫,它將一個新的 API 引入到一個舊的環境中,而且僅靠舊環境中已有的手段實現。
  2. polyfill 就是瀏覽器 API 的 shim 。 它用于實現瀏覽器并不支持的原生 API 的代碼,是抹平新舊瀏覽器對原生 API 支持差異的封裝。通常,polyfill 會先檢查當前瀏覽器是否支持某個 API,如果不支持的話就加載它自己的實現,然后新舊瀏覽器就都可以使用這個 API 了。相當于“打補丁”,“刮膩子”。

方案四、?? 輕量級庫 FastClick

  • 代碼:
    window.addEventListener( "load", function() {
        FastClick.attach( document.body ); // 直接綁定到 <body> 上可以確保整個應用都能受益
    }, false );
    
  • 原理:FastClick 在檢測到 touchend 事件的時候,會通過 DOM 自定義事件立即觸發一個模擬的 click 事件,并把瀏覽器 300ms 之后真正觸發的 click 事件阻止掉。
  • 無沖突:當 FastClick 檢測到當前頁面使用了基于 <meta> 標簽或者 touch-action 屬性的解決方案時,會靜靜地看別的解決方案裝逼
  • 唯一的缺點:文件大小占 10 KB……
  • 推薦使用!

關于“始作俑者” Safari

起承轉折

(2013) 300 毫秒點擊延遲的來龍去脈一文中提到的 IOS 特有的雙擊滾動(Double Tap to Scroll):仍存在、并沒有像原文猜測的那樣消失。(親測 IOS 10.2.1 Safari 已設置 <meta name="viewport" content="width=device-width"> 的頁面在屏幕上或下 1/4 處雙擊仍能滾動。)

起初看到「2016 年 3 月發布的 IOS 9.3 移除了 300ms 延遲、從而實現了“fast-tap” 」時,我還欣慰地想道:最先提出“雙擊縮放”約定的蘋果,在最后也順應了歷史潮流嘛。但是接著看到「IOS10 無視了禁用縮放(user-scalable)」我的內心瞬間黑人問號:“???”。

后來,靜靜地看了兩篇文章(Safari zoom gesture's comeback in iOS 10How to disable viewport scaling in iOS 10? You don't.),做了點 <meta> 標簽的測試。

測試結果

測試環境:IOS 10.2.1

  1. 只設置 <meta name="viewport" content="user-scalable=no"> 和不設置沒有任何區別——user-scalable=no 被完全無視。
  2. 只設置 <meta name="viewport" content="width=device-width"> ,和方案二里的描述一致,仍可以在初始尺寸下禁用雙擊縮放。
  3. 只設置 <meta name="viewport" content="initial-scale=1.0"> ,初始狀態和“測試2”很像,但是仍存在雙擊縮放,即仍有 300ms 延遲。
  4. 設置 <meta name="viewport" content="width=device-width, initial-scale=1.0"> 或者 <meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0"> ,和只設置 width=device-width 并無顯著差異。
  5. 為某個 <a> 鏈接設置 touch-action: manipulation; ,可以禁用該元素上的雙擊縮放。

要點如下:

  1. user-scalable=no 完全起不到禁止縮放的作用,width=device-width 仍能且僅能禁止雙擊縮放。
  2. 只設置 meta 無法完全禁用縮放,雙指縮放總是可行的。

暮然回首

冷靜下來后,重新審視上述變故,發現其實是兩回事。前面提到過“我們應該拋棄雙擊縮放、擁抱雙指縮放”,蘋果沒有打破這個原則。只是,蘋果出于可訪問性考慮,直接任性地完全無視了 user-scalable=no

Accessibility
Pinch-to-zoom is always enabled for all users. The viewport setting for user-scalable is ignored.

當然,這導致了覺得應該一切盡在掌控、想要完全禁用縮放以避免破壞布局的開發者的怨言。如果,你還是想完全禁用縮放,可以參考 SO 上的這個回答

解惑

最開始提到過,我至今沒有遇到過這個問題。對這個現象我推理如下:

我的腎機在開發移動端的半年間只在近期做過一次系統升級(目前已升到 10.2.1)。之前使用的具體的版本號已經無從得知了(P.S. 如果有誰知道怎么查看腎機本機上的版本更新歷史,請務必告訴我233),但是更新到 IOS10 之前,我一直有使用 9.3+ 才支持的 Night Shift 功能,也就是說升級之前的系統版本號肯定在 9.3 或者以上。

而我寫移動端頁面的時候,慣例會 meta:vp 然后 Tab 生成 <meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">。在 IOS10 之前,這行代碼還是能夠禁用頁面的縮放的,也就不存在 300ms 的延遲問題了。

這就是為什么之前我本機測試的時候一直沒有遇到傳說中的 300ms 延遲現象的原因了。

參考

  1. (2013) 300 毫秒點擊延遲的來龍去脈
  2. (2014) 5-ways-prevent-300ms-click-delay-mobile-devices
  3. (2015) Implement viewport-width-based fast-click heuristic
  4. (2016) 無線端瀏覽器 click 事件 300ms 延遲
  5. (2016) 300ms-tap-delay-gone-away
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容