標籤:代碼 ima 結果 api adjust 瀏覽器 需要 listener 解決方案
問題背景
很多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
。
於是很自然地想到,我們是否可以取到這些屬性呢?
// 取元素的fontSizedocument.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等用戶端既然提供調整字型的功能,必然是想用它來提供更好的體驗,不應該禁用。
- 從開發的角度來說,字型縮放之後,頁面會亂掉,根本原因在於頁面的適應性不夠,應該從代碼層面去最佳化。
- 繼續從開發的角度說,雖然理論上開發應該做好適配,但是對於文字突然被放大兩倍,很多時候確實心有餘而力不足。如果要做好,需要花費大量的時間和精力,並且需要設計和產品同學從設計上留出一些適配空間。
- 既然使用者選擇了用大字型來瀏覽頁面,他就應該知道這個頁面是被自己放大了,需要承擔頁面配置亂掉的結果。
移動端使用者佈建字型放大導致的問題