原文首發于 baishusama.github.io,歡迎圍觀~
存疑
最開始,我遇到的其實是“移動端遮罩層滑動穿透”的問題。
在查找“滑動穿透”問題相關資料的時候,我搜到了很多 click 300ms 延遲的問題。我那個時候有些不知所云,因為我自己并沒有真實遇到過 300ms 延遲現象,也就沒怎么在意。
時至今日想動筆總結遇到了若干次的“滑動穿透”問題的時候,搜集資料的偶然間得以解惑 300ms 的前世今生。
移動端 click
的 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 上說的,
viewport
的width
設置得小于等于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)支持的默認行為。這包括但不限于諸如平移或縮放等行為。 - 根據 MDN:
touch-action
的manipulation
值激活了平移和雙指縮放手勢,而禁用了雙擊縮放等非標準的手勢。 - 支持情況:在 Can I Use 上可以看出,除了 Opera Mini 不支持、FF 需要手動啟用和 Android 4.x 的自帶瀏覽器有些迷之外,其他瀏覽器支持良好。
- 推薦使用!
在只有 IE 支持指針事件的初期,誕生了不少指針事件的 polyfill 解決方案。在仍不支持指針事件的瀏覽器上,這是一種變通的方式。
shim
VSpolyfill
- 一個 shim 是一個庫,它將一個新的 API 引入到一個舊的環境中,而且僅靠舊環境中已有的手段實現。
- 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 10 和 How to disable viewport scaling in iOS 10? You don't.),做了點 <meta>
標簽的測試。
測試結果
測試環境:IOS 10.2.1
- 只設置
<meta name="viewport" content="user-scalable=no">
和不設置沒有任何區別——user-scalable=no
被完全無視。 - 只設置
<meta name="viewport" content="width=device-width">
,和方案二里的描述一致,仍可以在初始尺寸下禁用雙擊縮放。 - 只設置
<meta name="viewport" content="initial-scale=1.0">
,初始狀態和“測試2”很像,但是仍存在雙擊縮放,即仍有 300ms 延遲。 - 設置
<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
并無顯著差異。 - 為某個
<a>
鏈接設置touch-action: manipulation;
,可以禁用該元素上的雙擊縮放。
要點如下:
-
user-scalable=no
完全起不到禁止縮放的作用,width=device-width
仍能且僅能禁止雙擊縮放。 - 只設置
meta
無法完全禁用縮放,雙指縮放總是可行的。
暮然回首
冷靜下來后,重新審視上述變故,發現其實是兩回事。前面提到過“我們應該拋棄雙擊縮放、擁抱雙指縮放”,蘋果沒有打破這個原則。只是,蘋果出于可訪問性考慮,直接任性地完全無視了 user-scalable=no
。
當然,這導致了覺得應該一切盡在掌控、想要完全禁用縮放以避免破壞布局的開發者的怨言。如果,你還是想完全禁用縮放,可以參考 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 延遲現象的原因了。