移动端方案的特点

  如果说在PC端,我们可以用float/flex+定宽(如1000px)搞定布局,那么当下手机的型号尺寸多种多样,最流行的如iphone,华为,小米,VIVO等不同品牌的手机各自都有不同的型号,对应的多种多样的尺寸,这么多不同分辨率的手机,要一一适配工作量超级超级大了,简直无穷了。。可见,在手机端,通过定宽来搞定布局是不现实的,既然这样,可选的方案看上去只有百分比布局(所有元素尺寸相关的设置都是以百分比来设置)和整体缩放布局(不同尺寸中所有元素大小都是按比例放大/缩小)两种方式了

百分比布局



  上图中,是一个4个元素依次排列的列表,其中每个元素的宽度占屏幕总宽度的40%,中间空隙占据10%,左右空隙各占据5%,实现代码如下:

<ul class="container clearfix">
       <li>宽度为40%</li>
       <li>宽度为40%</li>
       <li>宽度为40%</li>
       <li>宽度为40%</li>
   </ul>

    
*{
  margin:0;
  padding:0;
}
.container li{
  background:#ccc;
  margin-top:10px;
  text-align:center;
  float:left;
  width:40%;
  margin-left:5%;
  margin-right:5%;
  list-style-type:none
}
.clearfix{
  display:block;
  content:'',
    clear:both;
}

复制代码

  现在li元素的宽度,外边距值等尺寸是按照百分比设定的,它们的比例确实也能够在屏幕尺寸缩放的时候保持不变,看上去貌似已经完美了,但到这里为止,其实忽略了一个严重的问题,高度怎么办呢?

  在这个例子中,我们并没有设置任何高度,如果需要让高度是宽度的1/2该怎么办?很遗憾,没有办法,在这里我们完全不知道宽度的尺寸(只知道一个比例),但我们又知道高度的设置并不像宽度一样,在没有显示指定父元素高度值的情况下,指定相对于父元素的百分比是无效的,所以说这种方式的一个最致命的缺陷就是没有办法灵活的控制各元素的高度。

缩放布局

  缩放布局,即根据比例的大小对原有尺寸进行缩放,那么具体应该如何实现呢?这里就用到了我们的REM—利用REM布局,把所有元素放大或缩小相同倍数,同时解决了不能灵活的设置高度的问题。

什么是REM

  和px,em,vh,vw一样,rem也是一种长度单位,rem即root em,即根元素html元素的font-size的大小,当设置font-size值为2rem时,则该元素字体大小为根元素html的font-size值的2倍。

  对于rem的应用,MDN中这样描述:

该单位在实际使用中一般用于创建完美的可扩展布局。如果不被目标浏览器支持,可以使用em单位,虽然会稍微复杂一些。

那么,rem和em到底有什么区别呢?

REM和EM的区别

  首先rem和em都是相对长度单位,我们已经知道rem,是根元素html元素的font-size的计算值,不同的是,em表示的是元素本身的font-size的计算值。

动态REM实现缩放布局




不同分辨率的元素尺寸换算

  图2是原始的UI图,屏幕大小是320px * 129px,各元素的相关尺寸值已经标出,当屏幕大小是414px*736px时,要实现所有元素尺寸的等比缩放,需要经过一定的换算,那么换算的机制是什么呢?

尺寸换算的机制

  尺寸换算的根本机制是—所有元素的尺寸相关值(包括元素宽度,高度,左右外边距,左右内边距等等),按照屏幕宽度的比例进行相应的缩放。不过我们知道屏幕大小涉及到屏幕的宽度尺寸和高度尺寸,而且往往不同屏幕之间的宽度比和高度比是不同的(如iphone7和iphone7 plus的宽度比是375/414,而高度比是667/736),那么为什么缩放要按照屏幕的宽度尺寸比进行缩放,而忽视屏幕的高度比呢?

  刚才也提到往往屏幕宽度比和高度比是不同的,也就意味着缩放比只能取其一,要么缩放比取高度比,则屏幕中元素的宽度方向不能与原设计稿完全匹配,要么缩放比取宽度比,则屏幕中元素的高度方向不能与原设计稿完全匹配,权衡之下,我们之所以取后者—宽度比作为方案,是因为,高度方向不匹配,可以依靠垂直方向的滚动条来作为后备补充,而如果宽度方向有滚动条总是需要有左右滑动的话未免显得太过怪异了。

使元素的尺寸和屏幕宽度相关

  既然元素的尺寸要随屏幕的宽度进行缩放,那么元素尺寸的单位到底应该是什么呢?常见的px,em,rem看似和屏幕宽度都没有直接关系,更别提能够随屏幕宽度的大小不同而进行缩放了,此时想到vw,就是把屏幕宽度分为了100份作为1个单位长度的,但是vw的兼容性比较差,也只能放弃了,可是问题总得解决,既然这些常见的长度单位没有直接关系,看下是不是某个单位能够和屏幕宽度由某种间接关系呢~

  如果让rem等于页面的html元素的font-size值,只要让font-size值等于屏幕的宽度,就把长度单位和屏幕的宽度联系起来了~ 不过要让html的font-size值屏幕的宽度,显然通过CSS指定一个固定值是不够的,因为我们分辨率的种类那么多,要自动识别屏幕的宽度,最好的办法就是通过JS得出宽度的计算值。

实现步骤

  综上,实现图2,图3的自适应屏幕的效果的代码如下:

<!DOCTYPE html>
<html lang="en">
<script>
  var pageWidth=window.innerWidth
  document.write('<style>html{font-size:'+pageWidth+'px;'}</style>')
</script>
<style>

*{

  margin:0;
  padding:0;

}
body{
  font-size:16px;
}
.container li{
  background:#ccc;
  margin-top:0.1rem;
  text-align:center;
  float:left;
  width:0.4rem;
  height:0.2rem;
  margin-left:0.05rem;
  margin-right:0.05rem;
  list-style-type:none
}
.clearfix{
  display:block;
  content:'',
    clear:both;
}
</style>
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>REM example</title>
</head>
 <body>
   <ul class="container clearfix">
       <li >宽度为40%</li>
       <li>宽度为40%</li>
       <li>宽度为40%</li>
       <li>宽度为40%</li>
   </ul>
   </body>
</html>
复制代码

通过js重置rem的单位长度

  通过js得到屏幕宽度,将该值作为style中的html的font-size属性值写入,关键代码如下:

var pageWidth=window.innerWidth
  document.write('<style>html{font-size:'+pageWidth+'px;'}</style>')

复制代码

将body的font-size值显示设置为16px

  在第一步中,通过js将单位长度的rem设置为屏幕宽度值了,不过还有一个问题是,因为将html的font-size值也设置为了屏幕宽度,此时所有元素的font-size值是继承自根元素html的font-size值的,岂不是所有文字都会很大,这个不是我们想要的效果,所以要解决和这个问题只需为body显示设置一下font-size值即可,这样所有body的子孙元素的font-size值都继承自body元素的font-size值了:

body{
    font-size:16px;
}

复制代码

将所有尺寸都换算为rem

  最后一步,只要按照UI图的尺寸,计算出各尺寸/屏幕宽度的值,该值即为应当设置的尺寸值(单位是rem)

  如UI图的尺寸是按照iphone7 Plus(414px*736px)设计的,如果一个元素的宽度是207px,则应对其设置width:0.5rem(207/414=0.5),当然了,前提了已经正确的设置了html元素的font-size值(为屏幕宽度值)~

对动态rem进行微调

  上例中动态rem实现移动端自适应分辨率的方式,看上去已经很完美了,不过还有一个稍微看上去别扭的地方就是,所有元素相关的尺寸貌似都是一个小数点值,如width:o.4rem;height:0.2rem,marin:0.05rem,这是因为单位长度实在太大了,1rem相当于屏幕的宽度,怎么可能不出现小数点呢,那么要想让这些尺寸为整数值应该怎么办呢?其实也简单~同样缩放,只需要把单位长度对应的值缩小10倍即可~

  此时,对应的设置根元素font-size值的js代码如下:

var pageWidth=window.innerWidth
  document.write('<style>html{font-size:'+pageWidth/10+'px;'}</style>')

复制代码

  相应的,元素尺寸看上去没有那么多小数值了(都乘以10)~

.container li{
  background:#ccc;
  margin-top:0.1rem;
  text-align:center;
  float:left;
  width:4rem;
  height:2rem;
  margin-left:0.5rem;
  margin-right:0.5rem;
  list-style-type:none
}

复制代码

  到此为止,元素尺寸的表现形式看上去优雅了很多,不过还是要经过复杂的尺寸换算才能将UI图中的px值换算为对应的rem值(UI中元素尺寸的px值/UI中的屏幕宽度px值*10),有没有什么更好的办法能省去这步换算呢?

px自动变为rem

  最简单的效果莫过于不需要经过任何换算,只需按照UI图中的px尺寸进行赋值即可达到根据屏幕宽度自适应的效果,但是这可以实现吗?原则上好像不可以,不过有了SASS就不一样了~

@function px2rem($px){
     @return $px/$uiScreenWidth*10 +rem;
 }
 $uiScreenWiddth:320;
......

 .container li{
  background:#ccc;
  margin-top:px2rem(32);
  text-align:center;
  float:left;
  width:px2rem(128);
  height:px2rem(64);
  margin-left:px2rem(16);
  margin-right:px2rem(16);
  list-style-type:none
}

复制代码

  如上所示,实现的关键在于定义px2rem函数,实时的把以px为单位的长度转换为rem

Tips

  1. 所有网页的默认字体大小是16px,同理若html元素没有显示指定font-size值,则它的计算值为16px
  2. 关于font-size值,Chrome浏览器比较特殊,它可以设置字体的最小值,如把Chrome的字号设置为12px,那么即使在CSS中显示将元素的font-size值设置的更小,它的计算值也是12px,所以尽量不要将font-size值设置的过小,可能会引发意外的bug(这也是为什么在js中我们写入的是'<style>html{font-size:'+pageWidth/10+'px;'而不是'<style>html{font-size:'+pageWidth/100+'px;')