移動端用戶設置字體放大導致的問題
問題背景
很多webview提供了調整頁面字體大小的功能,例如手機QQ、微信、部分Android內置瀏覽器等。大部分瀏覽器調整字體只會導致字體顯示大小發生改變,其他元素的大小不受影響。但對於結構稍微復雜一點的頁面,字體大小的變動就足以導致頁面布局亂掉,導致文本不居中、文字折行、布局混亂等問題。
調整字體大小功能24-mobile-browser-font-size/1.png)
作為前端工程師,碰到頁面亂掉的情況就會覺得很無辜了,明明是你自己放大的字體,放大了卻還要我來承擔排版亂掉的後果,多委屈啊。很久以前,在PC端,好歹我們還可以提示用戶按CTLR + 0將頁面的比例調整回來,現在在移動端,卻很難阻止用戶縮放字體大小。
然而即使再委屈,當問題來了之後還是需要處理的,誰讓我們是前端工程師呢?(笑)
原理
由於並不是很清楚各個平臺(瀏覽器)放大字體的機制,我分別咨詢了我們 iOS 和 Android 的同事,得知在調整字體大小時時,2個客戶端的處理方式不同。
iOS
iOS上需要調整 webview 的字體大小時,是通過給 body 設置 -webkit-text-size-adjust
屬性實現的:
iOS設置字體縮放代碼
既然這樣,我們應該可以通過JS取到這個屬性:
var body = document.body;
alert(body.getAttribute(‘style‘));
iOS獲取樣式
圖上可以看到,當頁面文字被放大時,確實多了一個-webkit-text-size-adjust
屬性。
Android
Android通過給 webview 設置字體的縮放來完成,具體的API是setTextZoom(int)
。
Android設置字體縮放代碼
我們通過一個demo頁面來查看效果:
Android設置字體縮放代碼
從圖中可以看到,文字確實是被放大了。例如“文字大小10px”這一段文字被放大了兩倍,隨文字一同被放大的還有以em
為單位的尺寸和line-height
。
於是很自然地想到,我們是否可以取到這些屬性呢?
// 取元素的fontSize
document.querySelector(‘.s10‘).style.fontSize;
結果很失望,取不到什麽有用的信息。
按iOS的方式,也取不到任何有用的樣式,可見Android webview中並不是使用-webkit-text-size-adjust
這個屬性來放大文字的。
一籌莫展之際,忽然想到是否應該取一下computedStyle
?
window.getComputedStyle(document.querySelector(‘.fs10‘),null).getPropertyValue(‘font-size‘)
這次終於有結果了,“文字大小10px”這一段文字明明白白地被使用了20px
的文字大小!
至此,我們可以大概推測出 Android webview 放大文字的原理:在CSS解析之後,渲染之前,將所有的字體大小的值進行縮放,後面的排版和渲染都會直接使用縮放後的CSS值。
解決方案
針對iOS,調整字體大小本身只是改變body
的css屬性,因此可以通過覆蓋樣式來控制。
body {
-webkit-text-size-adjust: 100% !important;
}
Android因為改變的是字體的大小,所以可以考慮將字體大小在設置的時候進行等比例縮小。例如,一個文字希望以10px
來進行渲染,當webview被放大兩倍時,此時font-size
會變為20px
。因此我們可以在取到這個放大比例之後,對原樣式進行等比縮小,比如將原文字大小設置為5px
,渲染的時候就變成了10px
。
var $dom = document.querySelector(‘.fs10‘);
var originFontSize = 10;
var scaledFontSize = parseInt(window.getComputedStyle($dom, null).getPropertyValue(‘font-size‘));
var scaleFactor = originFontSize / scaledFontSize;
$dom.style.fontSize = originFontSize * scaleFactor;
但是這樣做仍然有幾個問題:
- 一次只能操作一個DOM元素,無法批量處理
- 需要知道DOM元素原來設置的字體大小
這幾個問題並不如想象中的好解決。於是另辟蹊徑,看看是否有一勞永逸的辦法。腦海中很快冒出一個名詞——rem
!
如果我們的頁面字體大小都使用rem
進行聲明,那麽我們就只需要在頁面加載的時候根據縮放比例計算出html
元素的字體大小即可!詳見下方代碼:
(function(){
var $dom = document.createElement(‘div‘);
$dom.style = ‘font-size:10px;‘;
document.body.appendChild($dom);
// 計算出放大後的字體
var scaledFontSize = parseInt(window.getComputedStyle($dom, null).getPropertyValue(‘font-size‘));
document.body.removeChild($dom);
// 計算原字體和放大後字體的比例
var scaleFactor = 10 / scaledFontSize;
// 取html元素的字體大小
// 註意,這個大小也經過縮放了
// 所以下方計算的時候 *scaledFontSize是原來的html字體大小
// 再次 *scaledFontSize才是我們要設置的大小
var originRootFontSize = parseInt(window.getComputedStyle(document.documentElement, null).getPropertyValue(‘font-size‘));
document.documentElement.style.fontSize = originRootFontSize * scaleFactor * scaleFactor + ‘px‘;
})();
因為這段代碼中創建了一個元素,並放入了document.body
中,所以不能放在head
中運行。如果放在頁尾運行的話,則有可能會產生閃爍的情況,因此最好的辦法是將這段代碼放在<body>
開始的地方。
除了在Android webview以外,以上代碼在 Android 微信中實測也有效。
其它方案
Android微信
在編寫本文時,通過網上一些資料,發現在Android微信中,也可以借助WeixinJSBridge
對象來阻止字體大小調整。實測也有效。
(function() {
if (typeof WeixinJSBridge == "object" && typeof WeixinJSBridge.invoke == "function") {
handleFontSize();
} else {
document.addEventListener("WeixinJSBridgeReady", handleFontSize, false);
}
function handleFontSize() {
// 設置網頁字體為默認大小
WeixinJSBridge.invoke(‘setFontSizeCallback‘, { ‘fontSize‘ : 0 });
// 重寫設置網頁字體大小的事件
WeixinJSBridge.on(‘menu:setfont‘, function() {
WeixinJSBridge.invoke(‘setFontSizeCallback‘, { ‘fontSize‘ : 0 });
});
}
})();
Android QQ
作為用戶量龐大的APP之一,QQ也提供了禁止調整字體大小的方案,android qq中可以自定義webview顯示的控件,通過在url中加入指定參數即可。見如何定制手Q的Webview.
手機QQ文檔
理論上,www.futu5.com/?_wv=128訪問這個鏈接,功能菜單中不會出現調整字體大小的按鈕。但是,但是,但是,在我實測過程中,所有的參數中,就只有【128隱藏字體項不生效】。不知道是QQ的bug還是有意為之,目前已提交反饋,但未收到回應。
聲音和思考
在組內分享的時候,大家對於字體大小調整這個頭疼的問題各自有不同的看法,大概有怎麽幾種聲音:
- 從產品的角度來說,微信、QQ等客戶端既然提供調整字體的功能,必然是想用它來提供更好的體驗,不應該禁用。
- 從開發的角度來說,字體縮放之後,頁面會亂掉,根本原因在於頁面的適應性不夠,應該從代碼層面去優化。
- 繼續從開發的角度說,雖然理論上開發應該做好適配,但是對於文字突然被放大兩倍,很多時候確實心有余而力不足。如果要做好,需要花費大量的時間和精力,並且需要設計和產品同學從設計上留出一些適配空間。
- 既然用戶選擇了用大字體來瀏覽頁面,他就應該知道這個頁面是被自己放大了,需要承擔頁面布局亂掉的結果。
移動端用戶設置字體放大導致的問題