rem,作为一个低调的长度单位,由于手机网页的兴起,在屏幕适配中得到了重用。使用rem,前端开发者可以在各种屏幕尺寸下通过比例缩放轻松实现设计图需要的效果。rem的官方定义是“根元素的字体大小”,即根节点的字体大小作为计算长度的参考值。一般认为网页中的根节点是html元素,所以采用的方法是通过设置html元素的font-size来进行屏幕适配,但实际情况真的这么简单吗?首先我们看一下使用rem实现手机屏幕适配常用方案。设计稿的宽度为640px,即:designWidth=640,同时在640px的屏幕宽度下设置1rem=100px,即:rem2px=100。设置1rem=100px的好处不言而喻.前端开发者在截图重构页面时,直接将小数点后移,即可将UI图片中测得的px值转换成对应的rem值,方便快捷。另外,我们还在头部设置:viewport很有用很重要,但不是本文的重点,就不展开了。感兴趣的同学可以自行搜索。先来看看具体的方案:下面四种方案是同事分享的,原理是利用比例缩放——得到目标屏幕宽度与设计稿宽度的比值,作为rem的基值(缩放因子),并将其设置为html标签的字体大小。区别仅在于性能权衡和编写习惯。方案1@mediascreenand(min-width:320px){html{font-size:50px;}}@mediascreenand(min-width:360px){html{font-size:56.25px;}}@mediascreenand(min-width:375px){html{font-size:58.59375px;}}@mediascreenand(min-width:400px){html{font-size:62.5px;}}@mediascreenand(min-width:414px){html{字体大小:64.6875px;}}@mediascreenand(最小宽度:440px){html{字体大小:68.75px;}}@mediascreenand(最小宽度:480px){html{字体大小:75px;}}@mediascreenand(最小宽度:520px){html{字体大小:81.25px;}}@mediascreenand(最小宽度:560px){html{字体大小:87.5px;}}@mediascreenand(最小宽度:600px){html{字体大小:93.75px;}}@mediascreenand(最小宽度:640px){html{字体大小:100px;}}@mediascreenand(最小宽度:680px){html{字体大小:106.25px;}}@mediascreenand(min-width:720px){html{font-size:112.5px;}}@mediascreenand(min-width:760px){html{font-size:118.75px;}}@mediascreenand(min-width:800px){html{font-size:125px;}}@mediascreenand(min-width:960px){html{font-size:150px;}}方案2@mediascreenand(min-width:320px){html{字体大小:312.5%;}}@mediascreenand(最小宽度:360px){html{字体大小:351.5625%;}}@mediascreenand(最小宽度:375px){html{字体大小:366.211%;}}@mediascreenand(最小宽度:400px){html{字体大小:390.625%;}}@mediascreenand(最小宽度:414px){html{字体大小:404.2969%;}}@mediascreenand(最小宽度:440px){html{字体大小:429.6875%;}}@mediascreenand(最小宽度:480px){html{字体大小:468.75%;}}@mediascreenand(最小宽度:520px){html{font-size:507.8125%;}}@mediascreenand(min-width:560px){html{font-size:546.875%;}}@mediascreenand(min-width:600px){html{font-size:585.9375%;}}@mediascreenand(min-width:640px){html{font-size:625%;}}@mediascreenand(min-width:680px){html{font-size:664.0625%;}}@mediascreenand(min-宽度:720px){html{font-size:703.125%;}}@mediascreenand(min-width:760px){html{font-size:742.1875%;}}@mediascreenand(min-width:800px){html{font-size:781.25%;}}@mediascreenand(min-width:960px){html{font-size:937.5%;}}方案3vardesignWidth=640,rem2px=100;document.documentElment.style.fontSize=((window.innerWidth/designWidth)*rem2px)+'px';方案4vardesignWidth=640,rem2px=100;document.documentElement.style.fontSize=((((window.innerWidth/designWidth)*rem2px)/16)*100)+'%';为了避免理解上的混乱,我在上面的js代码中加上了()。实际代码中不必详细分析。rem和px直接换算公式可以写成:1rem=1*htmlFontSizehtmlFontSize是html元素的字体大小。先看方案1中屏幕宽度为640px时的设置:@mediascreenand(min-width:640px){html{font-size:100px;}}可以清楚的显示这个1rem=1*100px,和我们原来的设置一样.那么我们如何获取其他屏幕尺寸的htmlFontSize值。和方案3一样简单,因为我们采用的是比例缩放,所以我们可以计算出目标屏幕宽度和设计稿宽度的比例:window.innerWidth/designWidth*rem2px+'px'因为浏览器默认的字体大小为16px,所以当我们使用百分比作为根节点html的字体大小,即html元素的font-size值设置为百分比值时,rem的计算方式会改为:defaultFontSize=16px1rem=1*htmlFontSize*defaultFontSize如方案中2、屏幕宽度为640px时的设置:@mediascreenand(min-width:640px){html{font-size:625%;}}套用上面的公式:1rem=1*625%*16pxwhere:625%*16=6.25*16=100所以:1rem=1*100px同理可以得到html在所有屏幕尺寸下font-size值的计算公式,即解4:window.innerWidth/designWidth*rem2px/16*100+'%'通过方案三和方案四的公式,可以很方便的将方案一和方案二中的css产生。这里只给出方案三和方案四对应的验证页面(方案一和方案二是它们的变形):scheme3.html(http://htmlpreview.github.io/?https://github.com/hbxeagle/rem/blob/master/scheme3.html),scheme4.html(http://htmlpreview.github.io/?https://github.com/hbxeagle/rem/blob/master/scheme4.html)如下两个图为屏幕宽度为360px时的效果,计算目标为:1rem=56.25px。方案三的设置值为:56.25px,方案四的设置值为:351.5625%至此,似乎问题已经彻底解决,但实际情况当然是意外。在某些Android手机上,浏览器或webview的默认字体被系统设置的字体所改变。这会导致默认字体大于或小于16px。修改默认字号后,我们再来看方案三和方案四。同样当屏幕宽度为360px时,我们将系统的字体大小调大,如下图效果。设置前html元素字体大小的计算值为18px,设置后计算值为65px。由于屏幕宽度没有变化,所以我们的目标值,也就是我们在html元素上设置的font-size值没有变化,仍然是56.25px,但是最终计算出来的值有偏差。在分析偏差之前,先看看方案三和方案四在360px屏幕宽度下的计算过程:方案三:document.documentElement.style.fontSize=56.25pxhtmlFontSize=56.25px1rem=1*htmlFontSize=56.25px实际上是:1rem=64.6875px方案4:document.documentElement.style.fontSize=351.5625%htmlFontSize=351.5625%defaultFontSize=18px1rem=1*htmlFontSize*defaultFontSize=351.5625%*18px=63.28125px253%*rem12实际上是=64.6875px看来方案4的计算结果和实际效果很接近,而方案3的计算结果相差很大。下面对比一下方案三和方案四的计算公式://方案三document.documentElement.style.fontSize=window.innerWidth/designWidth*rem2px+'px';//方案四document.documentElement.style.fontSize=window.innerWidth/designWidth*rem2px/16*100+'%';选项4其实比选项3多了一个16,可以推测浏览器在计算rem的具体值时,如果html中设置的font-size是px值,会先除以16,再乘以通过htmlFontSize。1rem=1*(56.25px/16)*181*(56.25/16)*18=63.28125方案4有问题,因为系统默认字体改成了18px,但是我们计算百分比的时候,我们还是以16px为基准计算的值,所以有偏差(计算值和实际值有一点偏差,后面会提到)。在方案三中,我们其实并没有考虑浏览器的默认字体大小,但是在实际使用的过程中,浏览器还是去掉了16,此时默认的字体大小为18px。当设置html的fontSize为px时rem的计算公式如下:1rem=1*(htmlFontSize/16)*defaultFontSize当系统设置的字体大小改变时,defaultFontSize会随之改变,而16不会改变。因此,方案3虽然表面上没有考虑默认字体大小的变化,只关注屏幕与设计稿的宽度比例,但实际计算中仍然使用默认字体大小,仍然存在constant16在工作,导致场景3失败。所谓的“根元素”,其实并不是想象中的那样。一个是16,一个是18,毕竟拿的是根元素的字号。ok,在计算rem的时候,px方法会有一个不随系统字体大小变化的16,所以我们使用百分比方案来绕过这个问题。选项4使用百分比,因为16px的默认字体大小在计算中是硬编码的。所以它的偏差是没有动态获取默认字号。更新如下:Scheme4.1vardesignWidth=640,rem2px=100;varh=document.getElementsByTagName('html')[0];varhtmlFontSize=parseFloat(window.getComputedStyle(h,null).getPropertyValue('font-size'));document.documentElement.style.fontSize=window.innerWidth/designWidth*rem2px/htmlFontSize*100+'%';效果如下:在16px的图片中,设置后html的font-size与实际值1rem有偏差,同时6.4rem的计算值也有偏差。通过查看代码发现html的font-size使用的是:getPropertyValue('font-size')而1rem使用的是getPropertyValue('width')。偏差是浏览器在计算字体大小时进行了四舍五入。rem定义中的另一个元素“fontsize”不能按字面意思使用,宣告坏掉了。18px的偏差,与上述方案4中18px的实际值与计算值的偏差是同一个问题。因此,基准值仍需修正。在更新版本中,scheme4.2:vardesignWidth=640,rem2px=100;vard=window.document.createElement('div');d.style.width='1rem';d.style.display="none";varhead=window.document.getElementsByTagName('head')[0];head.appendChild(d);vardefaultFontSize=parseFloat(window.getComputedStyle(d,null).getPropertyValue('width'));d.remove();文档.documentElement.style.fontSize=window.innerWidth/designWidth*rem2px/defaultFontSize*100+'%';效果如下:至此解决了默认字体不是16px时rem的处理,考虑到还有设计Screenrotation,最终在手机上的解决方案是:functionadapt(designWidth,rem2px){vard=window.document.createElement('div');d.style.width='1rem';d.style.display="none";varhead=window.document.getElementsByTagName('head')[0];head.appendChild(d);vardefaultFontSize=parseFloat(window.getComputedStyle(d,null).getPropertyValue('width'));d.remove();document.documentElement.style.fontSize=window.innerWidth/designWidth*rem2px/defaultFontSize*100+'%';varst=document.createElement('style');varportrait="@mediascreenand(min-width:"+window.innerWidth+"px){html{font-size:"+((window.innerWidth/(designWidth/rem2px)/defaultFontSize)*100)+"%;}}";varlandscape="@mediascreenand(min-width:"+window.innerHeight+"px){html{font-size:"+((window.innerHeight/(designWidth/rem2px)/defaultFontSize)*100)+"%;}}"st.innerHTML=portrait+landscape;head.appendChild(st);returndefaultFontSize};vardefaultFontSize=adapt(640,100);再看rem的定义,“根元素的字体大小。”我们认为的根元素——html其实有也是影子在起作用,我们以为的font-size其实是一个近似值。