从原理到方案,一步步讲解web移动端实现自适应等比缩放
2019-11-09

  前言

  在移动端做自适应,我们常用的有媒体查询,rem ,em,宽度百分比这几种方案。但是都各有其缺点。

  首先拿媒体查询来说,在某一个宽度区间内只能使用一种样式,为了适应不同屏幕要,css的代码量就会增多,并且后期页面如果有改动,会变得越来越不易维护。em得根据父元素的字体大小来计算宽高,有很大局限性。用百分比来设置宽度局限性也大,首先是得计算每个元素占父元素的宽度,而且只能设置宽度的百分比,而高度则很难通过百分比来设置。所以最后的希望寄托在了rem上。

  rem与px

  在讲rem实现方案之前,我们还是按照国际惯例,讲一下rem与px之间的关系。rem是指相对于根元素的字体大小的单位。这句话怎么理解呢?请看下面的公式:

  元素的rem值 = 元素的px值 / 根节点字体大小 ,我们举栗子说明一下。

  如果我们设置了根元素的字体大小为13px,那么一个宽300px,高350px的元素对应的rem就是宽23.076923rem,高26.923076rem。

代码如下:

html{ font-size: 13px;}div{ width: 23.076923rem; // 23.076923rem = 300px / 13px height: 26.923076rem;// 26.923076rem = 350px / 13px}

可以通过查看器对上面的公式进行验证,如图

  可以看出,浏览器根据rem值自动计算得到div的宽高分别为300px,350px。由此我们可以把根节点字体大小作为自变量,元素宽高作为因变量(注意:此处的宽高指的是浏览器通过换算得到的元素px值,而不是rem值)。当我们通过JavaScript动态改变根节点字体大小时,浏览器就会重新计算元素的宽高,也就可以实现了动态缩放。但是怎么把缩放跟屏幕宽度联系起来呢?

  回答上述问题之前我们得知道,任何的缩放都必须有一个参考点,才能称为缩放,这是我们平时很容易忽略的一点。所以接下来我们以iphone6的设计稿为参考点,选择iphone6是因为现在大多数的UI出图都是iphone6的尺寸,我们写好css代码后,浏览器就可以在此基础上计算元素px值,从而达到缩放的效果。

 

同样通过例子来说明:UI图标注div宽300px, 高350px, 字体大小20px。

实现 

index.html   

<!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8"> <meta name="viewport" content="user-scalable=no, initial-scale=1, maximum-scale=1, minimum-scale=1, width=device-width, height=device-height"/> <title>Title</title> <link rel="stylesheet" href="./index.css"></head><body> <div> hello 大家 </div></body><script src="./src/index.js"></script></html>

 

index.css

div{ font-size: 0.533333rem; // 0.533333rem = 20px / 37.5px width: 8rem;        // 8rem = 300px / 37.5px height: 9.333333rem; // 9.333333rem = 350px / 37.5px margin: 0 auto; background-color: lightskyblue;}

 

 注意此处并没有设置根节的font-size为37.5px。但是计算rem时却使用了它,因为37.5px即为iphone6屏幕的十分之一,就是为了把iphone6作为参考基准,并且这样元素的宽高与屏幕的宽度之间就有了一个比例关系。当通过js通过改变font-size的值,并且保证这个值始终与屏幕宽度有一个比例关系时,浏览器根据公式重新计算元素的宽高就和屏幕宽度也就有了一个比例关系。当屏幕变宽,元素放大,当屏幕变窄元素缩小。

 

index.js

var htmlWidth = document.documentElement.clientWidth || document.body.clientWidth //获取屏幕宽度var htmlDom = document.getElementsByTagName("html")[0] //获取htmlhtmlDom.style.fontSize = htmlWidth / 10 + "px"; //设置html字体大小为屏幕的十分之一//监听窗口大小改变window.addEventListener("resize", () => { var htmlWidth = document.documentElement.clientWidth || document.body.clientWidth var htmlDom = document.getElementsByTagName("html")[0] htmlDom.style.fontSize = htmlWidth / 10 + "px";}) 

这样基本上就已经可以实现自适应了

 

但是还有一个重要的问题没解决。我们不能每一个元素的rem值都像上面那样挨个手动计算,这样效率太低。

下面介绍两种解决方案:

第一种:利用scss定义函数实现自动转换

新建index2.scss文件

@function px2rem($px){ $rem:37.5px; @return ($px / $rem) + rem;}div{ font-size: px2rem(20px); width: px2rem(300px); height: px2rem(350px); margin: 0 auto; background-color: lightskyblue;}

 

 然后看看IDE自动帮我们编译后的文件index2.css, 和index.css文件一模一样

div{ font-size: 0.533333rem; width: 8rem; height: 9.333333rem; margin: 0 auto; background-color: lightskyblue;}

 

 

 第二种:借助webpack,px2rem-loader实现

先下载各种loader

npm install style-loader css-loader px2rem-loader --save-dev

 

 配置webpack

{   test: /.css$/,    use: ExtractTextPlugin.extract({   fallback: "style-loader",   use: [{

  loader: "css-loader",     }, { loader: "px2rem-loader?remUnit=37.5&remPrecision=6" }], })}

 

 

 index3.css

div{ font-size: 20px; width: 300px; height: 350px; margin: 0 auto; background-color: lightskyblue;}

 

 通过webpack编译过后的index.css3文件还是和index.css文件一样。这样就直接可以按照ui图来写代码,不用任何计算。提高了效率