原文首發(fā)于 baishusama.github.io氧猬,歡迎圍觀~
存疑
最開始瘫寝,我遇到的其實是“移動端遮罩層滑動穿透”的問題国旷。
在查找“滑動穿透”問題相關資料的時候薇芝,我搜到了很多 click 300ms 延遲的問題。我那個時候有些不知所云务傲,因為我自己并沒有真實遇到過 300ms 延遲現象凉当,也就沒怎么在意枣申。
時至今日想動筆總結遇到了若干次的“滑動穿透”問題的時候,搜集資料的偶然間得以解惑 300ms 的前世今生看杭。
移動端 click
的 300ms 延遲
那么忠藤,這 300ms 延遲到底是從哪里來的呢?
時間要追溯到 2007 年初代 iPhone 發(fā)布前夕楼雹,蘋果為了解決“如何用手機這種小尺寸屏幕來顯示 PC 端網頁”這個問題模孩,提出了很多聰明的約定(convention)。而后因為 iPhone 的大獲成功贮缅,這些約定被各大手機瀏覽器爭相效仿榨咐。
這些約定之中,雙擊縮放(Double Tap to Zoom) 就是 300ms 的“元兇”——當用戶在頁面上 click 的時候谴供,瀏覽器為了判斷這個用戶操作是單擊還是雙擊块茁,會等待 300-350ms 。如果 300ms 內桂肌,發(fā)生了第二次 click 事件数焊,那么視為雙擊;否則為單擊崎场,等 300ms 時間過去之后佩耳,才觸發(fā) click 事件。
在那個還不存在響應式設計和雙指縮放(Pinch to Zoom)的時代照雁,這個延遲是一個合理的預防措施蚕愤。但不幸的是,這 300ms 的延遲已經成為用戶覺得 web 應用比 native 應用更慢饺蚊、性能不及后者的主要原因之一萍诱。諸如,鏈接污呼、按鈕裕坊、多選框等基于 click 交互的元素,以及 JS 對 click 事件的監(jiān)聽燕酷,都因此受到影響籍凝。
幸運的是,瀏覽器開發(fā)商(vendor)和開發(fā)者都注意到了這個問題苗缩,提出了一些解決方案饵蒂。
解決方案
方案一、?? 禁用縮放
- 代碼:
<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 不支持箱叁。而且墅垮,禁用縮放會損害移動端網頁的可用性和可訪問性。例如蝌蹂,可能無法放大網頁中的一張圖片或一段字體較小的文字噩斟。
這里要注意區(qū)分:“雙擊縮放”(Double Tap to Zoom)和“雙指縮放”(Pinch to Zoom)。為了兼顧消除 300ms 延遲和不損害可用性和可訪問性孤个,我們應該拋棄雙擊縮放、擁抱雙指縮放沛简。
方案二齐鲤、?? 視窗寬度設置為設備寬度
- 代碼:
<meta name="viewport" content="width=device-width">
- 原由:正如 Chromium Code Reviews 上說的,
viewport
的width
設置得小于等于device-width
的頁面椒楣,是針對移動端優(yōu)化過的或者是響應式的站點给郊,其內容足夠清晰,雙擊縮放失去了意義捧灰。因此淆九,為包含上面這行代碼的頁面禁用雙擊縮放。同時毛俏,雙指縮放得以保留炭庙,從而也就沒有可用性和可訪問性問題了。 - 支持情況:自 Chrome 32 開始煌寇,FF焕蹄、IE/Edge 也隨后支持了;2016 年 3 月阀溶,IOS 9.3 開始支持腻脏。
- 推薦使用!
該解決方案的“禁止雙擊縮放”是遵守如下規(guī)則的:
- 當頁面設置了視窗寬度為設備寬度且是初始尺寸(頁面尚未縮放)银锻,此時永品,雙擊縮放才是被禁止的。
- 如果視窗尺寸不是初始尺寸(頁面已經縮放)击纬,雙擊縮放是被允許的鼎姐。
- 為了在用戶結束縮放后仍能 fast-click ,縮小時,只能縮小到初始尺寸症见,而不是最小尺寸喂走。
方案三、?? 指針事件(Pointer Events)
- 代碼:
a, button, .myelements { -ms-touch-action: manipulation; /* IE10 */ touch-action: manipulation; /* IE11+ */ }
- 根據規(guī)范:CSS 屬性
touch-action
決定了觸摸輸入(touch input)能否觸發(fā) UA (User Agent)支持的默認行為谋作。這包括但不限于諸如平移或縮放等行為芋肠。 - 根據 MDN:
touch-action
的manipulation
值激活了平移和雙指縮放手勢,而禁用了雙擊縮放等非標準的手勢遵蚜。 - 支持情況:在 Can I Use 上可以看出帖池,除了 Opera Mini 不支持、FF 需要手動啟用和 Android 4.x 的自帶瀏覽器有些迷之外吭净,其他瀏覽器支持良好睡汹。
- 推薦使用!
在只有 IE 支持指針事件的初期寂殉,誕生了不少指針事件的 polyfill 解決方案囚巴。在仍不支持指針事件的瀏覽器上,這是一種變通的方式友扰。
shim
VSpolyfill
- 一個 shim 是一個庫彤叉,它將一個新的 API 引入到一個舊的環(huán)境中,而且僅靠舊環(huán)境中已有的手段實現村怪。
- polyfill 就是瀏覽器 API 的 shim 秽浇。 它用于實現瀏覽器并不支持的原生 API 的代碼,是抹平新舊瀏覽器對原生 API 支持差異的封裝甚负。通常柬焕,polyfill 會先檢查當前瀏覽器是否支持某個 API,如果不支持的話就加載它自己的實現梭域,然后新舊瀏覽器就都可以使用這個 API 了斑举。相當于“打補丁”,“刮膩子”碰辅。
方案四懂昂、?? 輕量級庫 FastClick
- 代碼:
window.addEventListener( "load", function() { FastClick.attach( document.body ); // 直接綁定到 <body> 上可以確保整個應用都能受益 }, false );
- 原理:FastClick 在檢測到
touchend
事件的時候,會通過 DOM 自定義事件立即觸發(fā)一個模擬的click
事件没宾,并把瀏覽器 300ms 之后真正觸發(fā)的 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 月發(fā)布的 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>
標簽的測試。
測試結果
測試環(huán)境:IOS 10.2.1
- 只設置
<meta name="viewport" content="user-scalable=no">
和不設置沒有任何區(qū)別——user-scalable=no
被完全無視伸但。 - 只設置
<meta name="viewport" content="width=device-width">
肾请,和方案二里的描述一致,仍可以在初始尺寸下禁用雙擊縮放更胖。 - 只設置
<meta name="viewport" content="initial-scale=1.0">
铛铁,初始狀態(tài)和“測試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
無法完全禁用縮放萌业,雙指縮放總是可行的坷襟。
暮然回首
冷靜下來后,重新審視上述變故生年,發(fā)現其實是兩回事婴程。前面提到過“我們應該拋棄雙擊縮放、擁抱雙指縮放”抱婉,蘋果沒有打破這個原則档叔。只是,蘋果出于可訪問性考慮蒸绩,直接任性地完全無視了 user-scalable=no
衙四。
當然,這導致了覺得應該一切盡在掌控患亿、想要完全禁用縮放以避免破壞布局的開發(fā)者的怨言传蹈。如果,你還是想完全禁用縮放,可以參考 SO 上的這個回答惦界。
解惑
最開始提到過挑格,我至今沒有遇到過這個問題。對這個現象我推理如下:
我的腎機在開發(fā)移動端的半年間只在近期做過一次系統升級(目前已升到 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 延遲現象的原因了。