下圖中是安卓微信中調整字體大小后的效果,行內的font-size:12px !important設置沒起作用,個人覺得是webview做了另外的處理,使用getComputedStyle獲取到的字體也確實是變大了(而且這是全局的,對所以元素都生效,包括頁面加載后append的元素),對設定了絕對值的元素則不影響。
很多webview提供了調整頁面字體大小的功能,例如手機QQ、微信、部分Android內置瀏覽器等。大部分瀏覽器調整字體只會導致字體顯示大小發生改變,其他元素的大小不受影響。但對於結構稍微復雜一點的頁面,字體大小的變動就足以導致頁面布局亂掉,導致文本不居中、文字折行、布局混亂等問題。
由於並不是很清楚各個平台(瀏覽器)放大字體的機制,我分別咨詢了我們 iOS 和 Android 的同事,得知在調整字體大小時時,2個客戶端的處理方式不同。
iOS
iOS上需要調整 webview 的字體大小時,是通過給 body 設置 -webkit-text-size-adjust 屬性實現的:
Android
Android通過給 webview 設置字體的縮放來完成,具體的API是setTextZoom(int)。
例如“文字大小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中運行。如果放在頁尾運行的話,則有可能會產生閃爍的情況,因此最好的辦法是將這段代碼放在開始的地方。
除了在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 }); }); } })();