“user-scalable=no”屬性被iOS Safari ignore的解決方法

Ashley Yang
7 min readMay 25, 2018

--

通常在mobile web的開發上,我們常常會在<meta name=”viewport” /> tag上加上user-scalable=no的屬性以避免放大縮小造成的跑版,事實上就我開發的經驗,如果排版得宜不亂用position跟flexbox,放大縮小本身並不大會造成跑版問題,但若是與mobile device橫向與直向螢幕rotate結合,每一次rotate時browser都會重新計算現在viewport要顯示的範圍,這的確會造成使用者不知道現在在哪的情形,這件事情有機會之後再來做討論。

在Mobile Safari 10.0之後的版本有一項重要的修改,預設ignore了”user-scalable=no”的設定,讓我們來看看這段敘述:

Accessibility

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

原先在mobile瀏覽器上,使用者有以下幾種方式可以放大或縮小網頁(zoom-in/zoom-out):

  1. Pinch to zoom-in/zoom-out
  2. Double-tap to zoom-in/zoom-out

同樣在header裡加入了<meta name=”viewport” content=”width=device-width, initial-scale=1, user-scalable=no”>,在iOS Chrome跟iOS Safari上的表現就不同,在iOS Chrome上無論是Double-tap跟Pinch都無法觸發網頁zoom-in/zoom-out,反之,在iOS Safari上Double-tap跟Pinch依舊會觸發網頁zoom-in/zoom-out。

解法

現在已經有不少解決方式,以下列出其中幾種的實際測試結果:

以JavaScript監聽並handle touchstart/touchend事件

window.onload = () => {
document.addEventListener('touchstart', (event) => {
if (event.touches.length > 1) {
event.preventDefault();
}
});

let lastTouchEnd = 0;
document.addEventListener('touchend', (event) => {
const now = (new Date()).getTime();
if (now - lastTouchEnd <= 300) {
event.preventDefault();
}
lastTouchEnd = now;
}, false);
}

實際測試結果:Pinch/Double-tap觸發的zoom-in/zoom-out均可成功handle

其中touchstart事件handle的是Pinch手勢,而touchend事件handle的則是double-tap手勢,講到double-tap就會想到mobile網頁開發時大家都會遇到的300ms延遲事件,事實上每一次使用者touch螢幕時,都會產生300ms的延遲去監聽是否觸發double-tap事件,於是就可以用這個特性來做處理,當兩次touch之間的時間差小於300ms時做event.preventDefault();直接cancel掉使用者double-tap手勢原先會觸發的zoom-in/zoom-out效果。

[2018.6.29 Update] Passive Event Listener

感謝網友回覆,上面這個workaround的Pinch在新版Safari仍然可以觸發zoom-in/zoom-out,剛好在公司專案也遇到的一樣的問題所以來做個update,在百思不得其解的過程之中發現了一個東西叫做Passive Event Listener,這是在大概2016年新發表的特性,目的是improve scroll時的performance,其中wheelmousewheeltouchstarttouchmove四個event的passive屬性default value都是true({ passive: true }),雖說是從Firefox跟Chrome開始的新東西,但是從browser support list可以看到目標browser iOS Safari已經support了這個屬性:

重點是當{ passive: true }時會導致preventDefault一點作用都沒有!

所以我們必須將touchstart的passive value覆寫為false,也就是將return value改為{ passive: false }

window.onload = () => {
document.addEventListener('touchstart', (event) => {
if (event.touches.length > 1) {
event.preventDefault();
}
}, { passive: false });

let lastTouchEnd = 0;
document.addEventListener('touchend', (event) => {
const now = (new Date()).getTime();
if (now - lastTouchEnd <= 300) {
event.preventDefault();
}
lastTouchEnd = now;
}, false);
}

上面這個做法實際試過是可以成功handle pinch/double-tap的zoom-in/zoom-out喔!

以CSS touch-action: manipulation禁止double-tap手勢

html, body {
touch-action: manipulation;
}

實際測試結果:只可handle Double-tap手勢觸發的zoom-in/zoom-out

這個CSS property可以禁止所有不合法的手勢,針對manipulation我們擷取其中的敘述:

manipulation

Enable panning and pinch zoom gestures, but disable additional non-standard gestures such as double-tap to zoom. Disabling double-tap to zoom removes the need for browsers to delay the generation of click events when the user taps the screen. This is an alias for "pan-x pan-y pinch-zoom" (which, for compatibility, is itself still valid).

換言之,pinch手勢不會被禁止,但是double-tap手勢會被禁止,測試結果也是只有double-tap的手勢觸發的zoom-in/zoom-out被handle了。

依據iOS發布的內容,這項更改是為了給使用者更好的瀏覽體驗,現今的網頁通常會用響應式設計來針對不同瀏覽模式給予使用者最佳的瀏覽模式,若是無法讓使用者在瀏覽時自由放大縮小網頁,似乎也造成了使用者的不便,最好的做法還是跟設計師及客戶確認好使用者的需求並做好兩者之間的權衡了!

--

--

Ashley Yang

Frontend Manager@Tomofun 十年經驗前端工程師 | 紀錄開發時遇到的問題及踩過的雷