什么是响应式?响应式的页面在不同的萤幕有不同的布局,换句话说,使用相同的 html 在不同的解析度有不同的排版。如下图所示:

  响应式布局是为了解决适配的问题,传统的开发方式是 PC 端开发一套,手机端再开发一套,而使用响应式布局只要开发一套就好了。因为它是用的同样 html,所以它的 JS 逻辑互动也只需写一套就好了,缺点是 CSS 比较重。
  传统的手机端适配常见有三种解决方案,种是 bootstrap 的 columns 布局;第二种是使用全域性的 rem,先根据萤幕换算 1rem 等于多少个 px,然后设定 html 标签的 font-size 为多少个 rem,萤幕越大,则 font-size 越大,然后页面所有的元素的宽高和字型大小都用 rem 等比例缩放;第三种是阿里的 flex box,这种方案和第二种类似,不同点是页面内容的字型大小是用的 px,而不是比例缩放的 rem 。种需要额外引入一个框架。第三种相对第二种来说应该更合理点,因为正文的字型常用的为 14px 或者 16px,如果一个页面在这个手机字号是 15.5px,在另外一个手机又变成了 14.9px,这样可能会有点奇怪。
  而使用响应式布局就不需要进行 rem 的换算,下面通过上图的那个例子一步一步地分析怎么做响应式。
  1. 设定不同解析度页面两边留白

  先一个页面的主体内容有大的宽度,当萤幕超过这个宽度时这个中间的主体内容大就这么大了,不会再变大了,也就是说它固定一个大宽度,然后居中显示,如大为 1080px 。然后当大于 1024px 时,页面主体内容小宽为 960px,两边自动留白;在 500px 到 1024px 之间两边保持留白 40px;而当小于 500px 时就认为是手机,两边留白 20px 。所以计算一下,container 的代码如下:

  总体的思想是留白要合适,既不能留太多,导致中间内容太窄,也不能让中间的内容显得太大。这个其实和 bootstrap 的 container 思想一致,只是您可能要根据您自己的业务特点、多用户人群等做不同留白策略。
  2. 萤幕变小时,一头变窄,另一头不变

  当萤幕变小或者浏览器视窗拉小时,中间内容的宽度就不能保持 1080px,它得跟着变小,而在变小的过程中,往往要保持一边不变,另一边随页面变窄,如下图所示:

  右边的结果栏宽度保持不变,左边的表单栏宽度缩小。因为右边一旦就窄不好看了,如果右边变窄,那么字型也要相应缩小,字号一缩小,右边上下留白就变得太大,这样就不美观了,所以只能采取右边保持不动的策略去缩小左边的内容。这种场景比较常见,右边如果是一个头像的话,它也不能跟着缩小,它一缩小高度也要跟着缩小,导致上下太空,所以这种情况也不能动。
  3. 保持中间留白固定,缩小内容宽度

  左栏的宽度变小应该怎么变呢?有一个原则,就是要保持中间的间距固定,而两边的内容宽度相应缩小,如下图所示:

  所以就要借助 CSS3 的 calc,如下所示:
  1
  2
  3
  input{
  width:calc((100%-20px)/2)
  }
  calc 的相容性 IE10 及以上支持,android 4 及以下不支持,所以考虑到不支持的装置,可以简单做个相容,如下代码所示:
  1
  2
  3
  4
  input{
  width:48%;
  width:calc((100%-20px)/2);
  }
  如果不支持 calc 就用 48%,这样差别其实不是很大,就是不是很精确。真的需要的话,您可以多写几个媒体查询变得更精确。
  4. 左右布局变成上下布局
  当萤幕拉得很小的时候,左栏已经缩得很小了,再变小就不协调了,所以这个时候要把左右布局改成上下布局,把右边的内容往下面放。因为右栏在大屏的时候是 float:right,所以在中屏的时候覆盖掉这个浮动的属性,变成 float:none 就可以了。原本右栏的内容有四行,都比较短,可以考虑把它下面的三行排成一行,即让它们浮动。如下面代码所示:
  1
  2
  3
  4
  5
  6
  7
  8
  9
  10
  11
  12
  13
  14
  15
  .cal-result{
  float:right;
  width:330px;
  }
  1
   media(max-width:800px){
  .cal-result{
  float:none;
  width:100%;
  }
  .cal-result.result{
  float:left;
  width:33%;
  }
  }

  让每一个 result 占 1/3,然后浮动,效果如下:

  5. 宽度太小时,自动换行
  特别是当内容是列表 ul 形式的时候,排不下的 li 应当自动换到下一行。当然也可以手动控制,如下:
  1
  2
  3
  4
  5
  6
  7
  8
  9
  10
  11
   media(max-width:800px){
  .result{
  width:33%;
  }
  }
  1
   media(max-width:400px){
  .result{
  width:50%;
  }
  }
  在萤幕宽度小于 400 的时候,每个结果就占 50%,这样就排成两行了。这也是一种常用的办法,但是在我们这个例子,如果数字比较小,在 iPhone6 375px 的萤幕上还是排得下的,如果能保持在一行相对比较美观。而且固定 50%,如果当数字比较大时也有可能会有重叠的危险,这个也有办法,就是别写死宽度,而是写死 min-width 为 50%,这样当内容比较长时,float 的元素同一行排不下就会自动换行。但是知名还是要个办法让它能根据内容长度自动换行,当然可以用 JS 计算,但是有点麻烦。
  这个时候 flex 就派上用场了,很简单,只要设定两个属性:
  1
  2
  3
  4
  5
  .result-container{
  display:flex;
  justify-content:space-between;
  flex-wrap:wrap;
  }

  space-between 让子元素挨着容器的两边等间距排列,而 wrap 属性让子元素自动换行,当容器宽度不够的时候,就有了以下的效果:

  这样还有一个小问题,就是当内容如果刚刚好占满时,两个项之间就没有间距了,如下图所示:

  这样就贴在一起了,由于 flex 的 space-between 不能指定小的 space,所以只通过 margin 或者 padding 的方法,如给元素新增 margin-right:
  1
  2
  3
  .result:not(:last-child){
  margin-right:10px;
  }

  效果如下:

  这样比贴在一起显示的效果好。
  还有从大屏变成成小屏的时候有些字号主要是标题的字号和间距要相应调小,这种变小是阶梯变化的,而不是像 rem 一样连续变化,而且这种阶梯一般只要有两个就够了,一个大屏的,一个小屏的。如果您需要做很多阶梯的话,那您的排版很可能有问题。
  6. 使用响应式图片
  如相同的头图,在电脑上需要使用大图,但是手机上面使用小图就好了,不然会造成手机上载入慢浪费流量等问题,一个办法是使用 backgound-image 结合媒体查询,如下所示:
  1
  2
  3
  4
  5
  6
  7
  .banner{
  background-image:url(/static/large.jpg);
  }
   media(max-width:500px){
  background-image:url(/static/small.jpg);
  }
  这种方法的缺点是对 SEO 站群不太友好,因为如果使用 img 标签还可以写个 alt 属性。
  第二种常用办法是使用 img 的 srcset 或者 picture 标签做响应式图片,这个我在《Effective 前端 7:加快页面开启速度》已经提到,这里不再重复。
  这种响应式图片除了大小屏之外,还可以兼顾视网屏即 dpr 为 2 及以上的和普通屏 dpr 为 1 的萤幕,即在高 dpr 的萤幕使用 2 倍图,而普通萤幕使用 1 倍图。
  7. 其它问题处理
  有些地方大小屏的排版差异比较大,例如有些内容大屏的时候是挨在一起,而小屏离得比较远,这个时候您可能得重复 html,写两份的标签,大屏的时候隐藏掉小屏的 html 标签,小屏的时候隐藏掉大屏的 html 标签。并且这种情况不应该是常例,如果您经常要写两套,那说明您这个页面可能不太适合写响应式,还不如直接写两套呢。
  还有个问题,有时候您可能要借助 rem/transform:scale 做大小缩放,但这一定是下策,我们的原则还是要保持字号和间距不变,当萤幕的跨度不是很大的时候。使用 transform 的后果是萤幕拉小的时候,内容跟着变小了,但是由于 transform 不会造成重排,它占据的高度还是那么大,下面的内容不会跟上来。这样就得手动计算内容的高度。另外如果使用 rem,就和响应式的思想冲突了。如果页面的一部分字号使用了 rem,另一部分字号使用了 px,这样就不协调了,如果您全部写 rem 那就不需要使用响应式开发了。这个时候您可能要想一想,是不是 UI 出得有问题。让 UI 重新调整。
  还有,有时候可能会用到高度的媒体查询,例如在高度小于多少的时候,不能让弹框超出页面的高度;在高度大于多少的时候,让 footer 的定位 fixed 在底部,不然 footer 的下面可能会留白。