1. 用户的需求
事情的起因是推广反馈到产品,目前在投放用户中有部分是会进行手机字体的放大缩小的,然后这部分群体反映我们的页面样式错乱,毫无章程。
比如微信中的字体大小:
如果用户进行了这部分操作,那么将会引发什么呢?下面是极端对比图:
所以产品沟通后希望可以在用户缩放字体的时候,不会使我们的页面发生错乱。(666)
2. 初步排查切入点
想要解决问题,那就得先知道问题出在哪里。从表面来看,页面样式出现错乱一般都是我们的长度单位有问题。
通过代码发现,该项目使用的是由px转rem单位,也就是说所有的单位都是相对于根节点字体大小改变的。
nice~那我重写一下rem的计算方式不就可以解决了咩。事实证明,是我年轻了!
2.1)什么是rem?
rem(font size of the root element)是指相对于根元素的字体大小的单位,不难理解到,只要我在根元素(这里一般我们根元素指body或html)设置了字体的大小font-size:**px
,那么所有的子元素的单位只要是rem,都将根据对应的倍数进行缩放。
eg:<html style="font-size: 16px"><el style="font-size:10rem"></el></html>
那么1rem=16px
则el对应的字体大小就是:16*10=160px!!!(吓死人的字体大小)
所以使用好rem,关键就在于如何定义html标签处的font-size
,如果你不定义的话,那么大多数浏览器默认都是1rem=16px
。
2.2)rem的计算
笔者这里的项目是引用了postcss库,以下是vue.config配置代码:
plugins: [
require('postcss-pxtorem')({
rootValue: 100,
propWhiteList: [],
minPixelValue: 2, // 转化后的小数点位数
}),
require('autoprefixer')(),
],
},
复制代码
这里有个rootValue: 100
,表示的就是rem和px的倍数关系是1rem=100px
,单单这样是不行的,如果这样的话,那我们前端书写代码的时候就没法根据UI给的单位进行编写了(得拿一个计算机不断归0归0…)。因此我们还得对单位进行一个换算,这一步的目的是和UI挂钩。所以请手动艾特你的UI…
换算的代码网络上五花八门,某宝某东等等一堆,但是万变不离其宗。笔者认为核心的思路就是,拿到我们的UI稿的大小!一般来说UI都是以iPhone6的物理像素宽为准,即:750px (为何是750px,详见2.3)。
所以我们核心的计算方法就是:fontSize = (clientWidth / 750) * 100 + 'px'
clientWidth表示设备的屏幕宽度,100就是rootValue的值,只要做到这一步,那么我们编程的时候就可以直接按照设计稿的长度进行填写了,美滋滋~
2.3)物理像素宽度和屏幕像素宽度
1px问题: 在我们平常的认知中,可能1px就已经是前端最小的单位了,比如1px的边宽,1px的距离等等,浏览器也告诉我们,没有0.5px。但是,1px在不同设备上的大小,还是有很大差距的。
举个极端的对比:1px的边宽在iPhone6上看到的效果和在小灵通手机(我们之前的翻盖手机)上看到的效果是一样的嘛?肯定不是(狗头)。
这是因为:1px是我们的css像素单位,它是一个抽象化的东西,在不同的设备之间,1个CSS像素所代表的物理像素是可以变化的。
假设我们将1px * 1px作为一个单位面积区域,不同设备不同分辨率对该区域的覆盖率不同:
我们可以看到,对低分辨率的设备来说,一个px单位面积覆盖的点只有一个,而更高分辨率的设备覆盖的点高达16个甚至更多。
所以这里引入了另外一个名词:
DPR:设备像素比,换算比例为: DPR = 物理像素 / 设备像素
。上面提到的iPhone6,其drp就是为2,所以375屏幕设备宽度对应的物理像素才是750px。
搞清楚这些后就可以着手对我的项目改造了。
3. 终究是我太年轻
首先看head头部,是否对用户的缩放进行限制。看到有:
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no">
所有都是1,并限制了用户的缩放手势了,ok下一步。
通过vconsole发现如下:
font-size一模一样没有改变!换句话说就是rem计算没问题!等等!
我们刚才说到rem的计算是根据设备的物理宽度/设计稿的宽度得到的。而用户的放大字体行为并没有改变到设备的物理宽度,所以这就解释得了为什么根字体大小是相同的了。
通过百度了解到:
IOS需要调整 webview 的字体大小时,是通过给 body 设置 -webkit-text-size-adjust 属性实现的
Android则是通过给 webview 设置字体的缩放来完成,相当于改变了DPR:
4. 解决方法
4.1)方法一
看到这里,我明白解决方法得区分Android和IOS了。首先IOS的非常简单,在我打包的index.html文件样式里加上
-webkit-text-size-adjust: none!important
强制IOS无法更改,以此达到保持页面大小原样,使得样式不错乱的效果。
对于Android,我们是无法去更改系统底层的设定的,那么有什么方法可以做到同IOS一样的效果呢?答案肯定是有的,以下内容来源于网络,侵权删:
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.appendChild($dom);
// 计算原字体和放大后字体的比例
var scaleFactor = 10 / scaledFontSize;
// 取 html 元素的字体大小
var originRootFontSize = parseInt(window.getComputedStyle(document.documentElement, null).getPropertyValue('font-size'));
// 由于设置 font-size 后实际会变大,故 font-size 需设置为更小一级
document.documentElement.style.fontSize = originRootFontSize * scaleFactor * scaleFactor + 'px';
复制代码
总结一下就是,既然Android改变了字体的大小,那么我去重新append一个设置好字体大小的元素进去,再去获取这个元素在实际页面中的字体大小,以此得出缩放比例,然后更改根元素的font-size,按照比例进行缩放,这样就可以做到保持页面大小原样,使得样式不错乱的效果了。
写到这里,基本问题得到解决。但是挑剔的读者可能发现这里有一个问题:
1. 触发了页面的回流,对于Android来说,在页面已经加载绘制完毕后,重新去改变根元素的字体大小,这无疑会触发浏览器的回流问题。
ps:回流(reflow):当浏览器发现页面某个部分发生了存在影响布局的变化,此时就需要倒回去重新渲染。我们称这个过程为回流。回流将会导致浏览器重新计算所有节点几何尺寸和位置。
4.2)解决方法二:
更换基础单位:rem变为vw。
在官方文档上对于vw的描述如下:
vw unit: Equal to 1% of the width of the initial containing block.
复制代码
简单来说就是:1vw=1%屏幕视图宽度。
为什么说vw可以替换rem进行适配,举个简单的例子:
eg:以iPhone6为基础的设计稿物理像素为750px,如果上面一个375px的元素,那我们写的代码就是:width: 375px
,再通过工具转化的话得到:width:3.75rem
。这样的话如果在实际iPhone6中,根元素字体大小根据计算应为:font-size: 50px
,对应的元素宽度大小为:50*3.75=187.5px,而iPhone6的设备宽度是375,刚好符合设计稿的比例。
但是我们要知道这是在用户没有进行字体缩放的前提下,如果用户进行了放大,那么就相当于变大了DPR,根据2.3我们知道,DPR = 物理像素 / 设备像素
,在设备像素375不变的情况下,物理像素变大了,所以得到的实际元素宽度大于187.5px。
看到这里的小伙伴们应该发现了,实际宽度是受物理像素影响的,那如果我使用vw呢?它相对的是设备的宽度,既:1vw = 1%屏幕宽度!
至此,问题完美解决。
后记
笔者对问题的一个收集整理,如果有问题可以指出。