前言
部门移动端布局采用的是rem+font-size的方案,这种方案中font-size的值至关重要。正常情况下页面不会出现布局错乱现象,但是部分android机,通过改变系统默认字号(若改成大号字体)会导致font-size值被放大,相当于乘了一个放大因子scale,从而导致页面布局错乱,本文就是解决这个问题。
(所谓老人机,这里指手动修改了系统默认字体或缩放设置)
举例
数据来源:三星S8手机
系统缩放设置 | 系统字体设置 | clientWidth | font-size(clientWidth/7.5) | 布局是否正常 |
---|---|---|---|---|
小 | 小 | 412 | 54.93 | 否 |
小 | 中 | 412 | 54.93 | 否 |
小 | 大 | 412 | 54.93 | 否 |
中 | 小 | 360 | 48 | 是 |
中 | 中 | 360 | 48 | 否 |
中 | 大 | 360 | 48 | 否 |
大 | 小 | 320 | 42.67 | 否 |
大 | 中 | 320 | 42.67 | 否 |
中 | 大 | 320 | 42.67 | 否 |
结论:
1、系统缩放设置(部分手机能设置该项)可改变clientWidth(取自document.documentElement.clientWidth);
2、同缩放下,系统默认字体被修改后,虽然font-size值没变,但在渲染时实际上被隐式放大或缩小了,记为缩放因子scale;
方案
思路:这个缩放因子scale很重要,它是导致页面布局错乱的罪魁祸首,如果知道这个scale,那么取其倒数,就可以恢复正常。
先看看rem+font-size的布局套路:
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width,minimum-scale=1.0,maximum-scale=1.0,user-scalable=no">
<title>墨粉club</title>
<script>
(function() {
var maxLimitW = 768,
remCount = 7.5,
clientW = document.documentElement.clientWidth||document.body.clientWidth;
var initFontSize = Math.min(maxLimitW, clientW) / remCount;
document.documentElement.style.fontSize = initFontSize + 'px';
})();
</script>
</head>
由上表可知,缩放程度为中,默认字体为小时,页面正常显示。现在考虑缩放程度为中,默认字体为中的情形:
字面上计算的font-size没变(记为F1),但实际上渲染时的font-size是被放大了的,记为(F1=F2*scale),得出scale=F1/F2;
关键点:scale还可以这么计算,正常情况下,我们认为页面宽度被分为7.5份,但缩放后页面肯定不是7.5份了,假设缩放后页面宽度被分为N份,则scale*N=7.5,得scale=7.5/N;所以有7.5/N=F1/F2,从而有F2=F1*N/7.5;只要计算出N,就能计算出F2。
下面就是如何计算N:
document.addEventListener('DOMContentLoaded', function() {
var remFixDom = document.createElement("div");
remFixDom.style.cssText = "width:100%;height:1rem;opacity:0;position:absolute;z-index:-9999;";
document.body.appendChild(remFixDom);
var render = window.getComputedStyle(remFixDom);
var N = (render.width.slice(0, -2) / render.height.slice(0, -2)).toFixed(1);
setTimeout(function() {
remFixDom.style.cssText = "height:0";
}, 1000)
});
算出F2后,设置为html的font-size=F2即可修复该问题。
完整代码:
(function() {
var maxLimitW = 768,
remCount = 7.5,
clientW = document.documentElement.clientWidth,
hasReadyInit;
// 兼容三星S8 bug,只有第一次进入时才能取得clientW的值,第二次进该值为0,所以用localStorage存起来
if (clientW) {
localStorage.setItem('clientW', clientW);
} else {
clientW = localStorage.getItem('clientW');
}
var initFontSize = Math.min(maxLimitW, clientW) / remCount;
document.documentElement.style.fontSize = initFontSize + 'px';
// 修正系统设置了字号之后,支持动态字体的APP会强制调整网页font-size,导致rem方式的适配乱版问题(比如下面ua)
//eg. 华为QQ内置浏览器 Android 7.0; EVA-AL00 Build/HUAWEIEVA-AL00; wv) Mobile MQQBrowser/6.2 QQ/7.1.5.3215
if (clientW >= maxLimitW) return;
if (hasReadyInit) return; //已经注册过ready修正事件了
document.addEventListener('DOMContentLoaded', function() {
var remFixDom = document.createElement("div");
remFixDom.style.cssText = "width:100%;height:1rem;opacity:0;position:absolute;z-index:-9999;";
document.body.appendChild(remFixDom);
var render = window.getComputedStyle(remFixDom);
var rRate = (render.width.slice(0, -2) / render.height.slice(0, -2)).toFixed(1);
var finalFontSize = initFontSize * (rRate / remCount);
if (finalFontSize) {
localStorage.setItem('finalFontSize', finalFontSize);
} else {
finalFontSize = localStorage.getItem('finalFontSize');
}
if (rRate != remCount) document.documentElement.style.fontSize = finalFontSize + "px";
hasReadyInit = true;
setTimeout(function() {
remFixDom.style.cssText = "height:0";
}, 1000)
});
})();
网友评论