Saber 酱的抱枕

Fly me to the moon

01/22
2017
学习

动态设置rem使页面自适应缩放

更新:两年过去了,现在推荐使用 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获取。

动态设置rem使页面自适应缩放