更新:两年过去了,现在推荐使用 vw + rem 自适应布局。
我们页面布局中,可以通过动态设置rem并使用rem单位来设置宽高,来使得页面元素的尺寸可以随设备宽度而变化,这样就能保持布局的一致性。
使用JavaScript动态设置rem的代码如下:
(function (doc, win) { var docEl = doc.documentElement, resizeEvt = 'orientationchange' in window ? 'orientationchange' : 'resize', recalc = function () { var clientWidth = docEl.clientWidth; if (!clientWidth) return; docEl.style.fontSize = 100 * (clientWidth / 640) + 'px'; }; if (!doc.addEventListener) return; win.addEventListener(resizeEvt, recalc, false); doc.addEventListener('DOMContentLoaded', recalc, false); })(document, window);
公式是这样的:
100 * ( 浏览器页面宽度 / 设计稿宽度 )
第一部分:100是什么
这个100其实是我们使用的rem基础值,等于设置html标签的font-size=100px,这样,1rem也就是100px。
当1rem为100px时,页面上所有元素设置宽高大小时都除以100,就得出了该元素的rem值。比如设计稿上300px宽的元素,用rem设置就是width=3rem。
1rem并不是非要设置为100px,我们也可以设置成别的,比如设置1rem=50px,那么300px的元素是6rem。我们一般取1rem=100px是为了方便计算。如果实际项目中计算页面元素的rem值时不是按1rem=100px来算的,则需要修改公式里的100为实际使用的1rem值。
第二部分:浏览器页面宽度 / 设计稿宽度 是什么
这个可以理解为页面在设备上的缩放比例。比如iphone 6 plus的设备宽度是414px,我们的设计稿是640px。
414/640=0.646875,得出我们的页面在iphone 6 plus上应该显示为设计稿大小的0.646875倍。
最后我们把rem的基础值与页面缩放比例相乘,得出实际使用的rem基础值。rem的基础值变了,页面上所有元素的宽高、大小也会随之改变,这样就达到了页面自适应的效果。
上面讲完了计算的理论,但实际使用时还要考虑其他条件:
1.设备检测
用rem把页面在手机上缩放至全屏当然没问题,但用电脑看的时候,如果页面放大到全屏,会很糟糕的。所以建议用JavaScript代码检测一下设备,只在移动设备上进行缩放。
检测的正则如下(如果有更好的检测办法欢迎提出):
/Mobile/i.test(navigator.userAgent)
PS: bilibili检测是否是PC的代码:
var ua = window.navigator.userAgent, agents = ['Android', 'iPhone', 'SymbianOS', 'Windows Phone', 'iPod'], isPC = true; for (var i = 0, len = agents.length; i < len; i++) { if (ua.indexOf(agents[i]) > 0) { isPC = false; break; } }
2.减少页面重绘的不良影响
上面的重设rem的代码的执行时机是:1.DOM加载完成后;2.窗口尺寸改变时。
这样的话,在DOM加载完成前,页面是用的预设的rem;DOM加载完成后,变成了计算后的rem。这样就会引起页面重绘。要避免浏览者看到页面重绘,则需要尽早设置合适的rem。我们可以在<body>开始标签后面,加上一句立即设置rem的代码:
document.documentElement.style.fontSize=100 * (window.screen.width / 640) +"px";
我们可以把这句代码加到整体的代码的前面,之后把整体的代码放在<body>开始标签后面就可以了。
实际使用时如下:
1.先设置meta信息:
<meta name="viewport" content="width=device-width, initial-scale=1" />
2.在css里先给html预设一个字号,如100px。因为pc端我们没有计算rem值,所以要定义一个预设值给pc端用。之后给body设置pc端使用的样式。
综合起来如下:
html{font-size: 100px;} body{width: 6.4rem;margin-left: auto;margin-right: auto;}
3.在<body>的开始标签后面加上如下js代码:
// 重设rem let designWidth = 640; // 设计宽度,根据需要修改 let docEl = document.documentElement; if (/Mobile/i.test(navigator.userAgent)) { let one_size = 100 * (window.screen.width / designWidth); //在移动端,先动态设置一个rem值,防止画面抖动 if (one_size > 100) { one_size = 100; } docEl.style.fontSize = one_size + 'px'; // DOM加载完成后以及窗口尺寸发生变化时重设rem let resizeEvt = 'orientationchange' in window ? 'orientationchange' : 'resize'; window.addEventListener(resizeEvt, resetREM, false); document.addEventListener('DOMContentLoaded', resetREM, false); } function resetREM() { let clientWidth = docEl.clientWidth; if (!clientWidth) return; docEl.style.fontSize = 100 * (clientWidth / designWidth) + 'px'; // 第二次重设 let two_size = parseFloat(docEl.style.fontSize) / (parseFloat(window.getComputedStyle(document.body, null).getPropertyValue('width')) / parseInt(window.screen.width)); if (two_size > 100) { // 限制最大字号不超过100 two_size = 100; } docEl.style.fontSize = two_size + 'px'; }
最后在写样式时使用rem就行了。
注意:
使用rem做自适应比起其他一些办法可能要多做两步,主要是页面缩小后要保证图片也缩小,否则图片就显示的太大了。
1.所有img图片必须用rem设置具体宽高(或者设置 img{max-width:100%}),否则图片宽高用的就是默认的px单位,不受rem的约束,导致超大。
2.所有背景图片必须设置background-size,否则也基本上都是显示的超大。可以和img一样,把background-size设置为它的宽高数值。
3.如果要用背景定位background-position,照常把px转换为rem数值就行。但是页面缩小后,计算值可能产生小数点,这个略坑。
ps:一些后台的文本编辑器会把插入的图片自动设置px的宽高,但是我们用的是rem的单位,px的会产生冲突。可以在文章页加入以下js代码,把图片的宽高用rem重设:
// 将文章里的图片转换为rem单位 var articleImages=document.querySelectorAll("article img"); if (!!articleImages) { var rem=parseFloat(getComputedStyle(document.querySelector("html"),false)["fontSize"]); for (var i = 0; i < articleImages.length; i++) { articleImages[i].style.width=parseInt(articleImages[i].style.width)/rem+"rem"; articleImages[i].style.height="auto"; } }
上面代码里有个“第二次重设”,是为了应对奇葩情况。
比如华为很多手机对rem计算不准确。我拿它和小米对比一下:
设备宽度都是360px,计算出来根元素的字号都是52.65px,然后某个元素的宽度设置为6.4rem(即全屏宽度)。这样计算出来这个元素实际宽度是多少呢?
华为是奇葩的324px(实际占预算的90%)。小米之类大多数手机则是正常的360px。所以碰上华为这类奇葩,就算我们预设了正确的根rem值,但是华为算的时候是0.9倍率,就铺不满屏幕。
怎么办呢?我们要在最后检查一下这个我们期望是全屏的元素的宽度,然后把它的实际宽度除以屏幕宽度,得出这个奇葩手机的计算比例(如华为的0.9),再根据这个比例重新设置根rem。
“第二次重设”的代码分解如下:
// 现在html根元素的fontSize var nowRem==parseFloat(document.documentElement.style.fontSize); // 获取屏幕宽度 var screenWidth=parseInt(window.screen.width); // 获取本该是全屏宽度的元素的实际宽度 var trueWidth=parseFloat(getComputedStyle(document.body, null).getPropertyValue('width')); // 按实际比例,重新设置html根元素的fontSize document.documentElement.style.fontSize=nowRem/(trueWidth/screenWidth);
相关知识:
device Pixel Ratio(设备像素比,或简称DPR),在JavaScript里可使用window.devicePixelRatio获取。
现在再看一遍