响应式与移动端(二):了不起的百分比


我们通常把使用百分比的布局称为流动布局,但这不重要,重要的是要理解如何更好地使用百分比。

一、宽度百分比

比如我们写了一个宽度为1000px的布局(通常我们把这种用px的布局叫做固定布局),但是当窗口宽度小于1000px时,显然会截掉部分内容。为了让内容能够随着浏览器窗口的大小的变化全部显示,我们需要使用百分比来布局。此外,你可能已经知道通过响应式中有名的media queries可以在某个分辨率节点改变布局,那么使用百分比可以让处于这些节点之间的设备屏幕或是浏览器窗口也能正常显示全部内容。

接下来,我们通过下图来看一下如何把一个固定布局转换为一个流动布局:
layout
上图中每块内容都通过除法运算得出一个百分比的宽度,一个基本的公式就是“固定布局的宽度/基准参考值=流动布局百分比”。这里的关键点在于这个“基准参考值”,图中所取都是1000px,因为1000px是他们的父级块的宽度,所以,想要求出当前元素的百分比,他的基准参考值应该是父级块的宽度。举个具体的例子:

<div class="A">
  <div class="B">
    <div class="C"></div>
  </div></div><style>.A{  width: 1000px;
}.B{  width: 50%; /*500px/1000px*/}.C{  width: 50%; /*250px/500px*/}</style>

上面代码中,A的固定宽度是1000px;B的父级块是A,B固定宽度是500px,根据公式可得出B的百分比宽度是50%;C是父级块是B而不是A,C固定宽度是250px,那么C的百分比宽度也是50%。

根据这个原理,我们来实现上图中的布局。首先HTML代码如下:

<!DOCTYPE html><html><head>
  <meta charset="utf-8">
  <title>宽度百分比布局</title></head><body><div class="w-main">
  <header></header>
  <section>
    <aside></aside>
    <article></article>
    <aside></aside>
  </section>
  <footer></footer></div></body></html>

我们在body里面包了一层.w-main方便后面的修改。CSS代码如下(注释中为固定布局的样式属性或值):

.w-main{  max-width: 1000px; /*width: 1000px;*/
  margin: 0 auto;
}header{  width: 100%; /*1000px;*/
  height: 100px;  background: #B61B17;
}section{  margin-top: 2%;/*20px;*/
  overflow: hidden;
}aside{  float: left;  width: 28%; /*280px;*/
  height: 500px;  background:#317F97;
}article{  float: left;  width: 40%; /*400px;*/
  margin: 0 2%;  height: 500px;  background: #73AA47;
}footer{  width: 100%; /*1000px;*/
  height: 100px;  margin-top: 2%; /*20px;*/
  background: #E99528;
}

最终的Demo在这里。代码中有几点要提一下:

我们把.w-main中的width:1000px换成了max-width: 1000px,这样它就可以作为其子元素在计算百分比时的具体参考值了。max-width只有ie6不兼容,如果你因为某种原因实在要把这种兼容性考虑在内,请查看这里。一般限定了内容宽度的页面都会居中,我们用margin: 0 auto就可以让一个块水平居中,前提是这个块一定要指定宽度。所以max-width此时也帮到我们让内容水平居中。那么当浏览器窗口宽度小于1000px会发生什么事呢?max-width已经失去效力,起作用的是CSS中的继承特性。.w-main的宽度(width)会继承body的宽度,body会继承html的宽度,html会继承window的宽度。但是要注意,行内元素(display:inline)、行内块元素(display:inline-block)、绝对/固定定位元素(position:absolute/fixed)是不会自动继承父级块的宽度的,如果你不显式指定width那么它们的宽度是靠内容撑开的。所以.w-main的宽度在窗口小于1000px的情况下是跟窗口的宽度相等的。综上,这个页面会在窗口大于1000px时居中,在窗口小于1000px时各个块按比例撑满显示。另外,我们也没有指定section的宽度,它一直都是继承.w-main的。

你应该已经发现所有margin的取值也是百分比。从上图中得知取得这个百分比的基准参考值也是1000px。所以此处应用了一个css原理,那就是当margin、padding的单位为%时,它们是基于父级块的宽度的百分比,而无论你设置的是margin或padding的上下左右哪个属性。以上完整代码也可以在JSBin上看到。

上例中我们的高度(height)还是用的固定的px,一般情况下,响应式的布局不会这么僵硬,高度是靠文字内容和图片自动撑开的。 但是,高度也是可以设置百分比的。

二、高度百分比

高度的百分比是相对于父级块的,但是父级块的高度要显式指定,其子元素的高度百分比才会起作用,否则的话设置了百分比高度的子元素其高度会重置为auto。这也就是为什么我们习惯于同时给html和body设置height: 100%,这样就可以让body内部的子级块使用百分比高度。

三、图片和视频

3.1 前景图片

如果不为img指定宽高,默认情况下,img的宽高会是图像的原始尺寸。因此相对其父容器可能溢出,也可能不满。也不会跟着父容器的百分比发生变化。为了能让img跟着父容器的宽度而变化,我们常常给它设置如下样式:

img {    width: 100%;    height: auto;
}

如上设置,img的高度会自动保持比例。因此我们一般不指定img外层容器的高度,而是让其内部的img自动撑开。此外还有一种很有用的样式:

img {    max-width: 100%;    height: auto;
}

注意,上面的代码我们并没有指定width,而是指定了max-width:100%。这样的话,图片的最大宽度不会超过其原始宽度,而图片在小于原始宽度的情况下会按width: 100%的样子显示。 本篇博客的配图就是用了这样的样式。

对于img元素,还有个3像素“bug”,默认情况下图片下方会多出3px的边距,可以通过为img指定vertical-align的任意值或者是display:block来消除这3px,而一般使用vertical-align: middle不会破坏img默认是行内块的样式。

下图是一个经典的图片布局:

birds

首先我们来看一下html结构:

<!DOCTYPE html><html><head>
  <meta charset="utf-8">
  <title>固定间距多列布局</title></head><body><div class="w-main">
  <div class="list">
    <!--三列-->
    <div class="c3">
      <div class="c-inner"><img src="http://img0.bdstatic.com/img/p_w_picpath/shouye/systsy-11973574263.jpg" alt=""></div>
    </div>
    <div class="c3">
      <div class="c-inner"><img src="http://img0.bdstatic.com/img/p_w_picpath/shouye/systsy-11973574263.jpg" alt=""></div>
    </div>
    <div class="c3">
      <div class="c-inner"><img src="http://img0.bdstatic.com/img/p_w_picpath/shouye/systsy-11973574263.jpg" alt=""></div>
    </div>
    <!--/三列-->
    
    <!--两列-->
    <div class="c2">
      <div class="c-inner"><img src="http://img0.bdstatic.com/img/p_w_picpath/shouye/systsy-11973574263.jpg" alt=""></div>
    </div>
    <div class="c2">
      <div class="c-inner"><img src="http://img0.bdstatic.com/img/p_w_picpath/shouye/systsy-11973574263.jpg" alt=""></div>
    </div>
    <!--/两列-->
    
    <!--四列-->
    <div class="c4">
      <div class="c-inner"><img src="http://img0.bdstatic.com/img/p_w_picpath/shouye/systsy-11973574263.jpg" alt=""></div>
    </div>
    <div class="c4">
      <div class="c-inner"><img src="http://img0.bdstatic.com/img/p_w_picpath/shouye/systsy-11973574263.jpg" alt=""></div>
    </div>
    <div class="c4">
      <div class="c-inner"><img src="http://img0.bdstatic.com/img/p_w_picpath/shouye/systsy-11973574263.jpg" alt=""></div>
    </div>
    <div class="c4">
      <div class="c-inner"><img src="http://img0.bdstatic.com/img/p_w_picpath/shouye/systsy-11973574263.jpg" alt=""></div>
    </div>
    <!--/四列-->
  </div></div></body></html>

为什么我们要给img套两层div?第一层.c2、.c3、.c4是管理列数的(c是column的缩写),是n列那么它的宽度就是100除以n得到的百分比,再让它们向左浮动,就排成了一行。第二层.c-inner是管理内边距、外边距、边框的的,我们没有给它指定宽度,根据上文中所讲的,它的宽度会继承父级块。但是,无论你为.c-inner指定padding、margin还是border-width,这些值与它自身的实际的width之和,总是等于父级块容器的width。就拿.c2来说:.c2的width = .c2内部的.c-inner的margin-left + border-left-width + padding-left + width(未设置) + padding-right + border-right-width + margin-right。其实,.c2的width是显示指定过的,我们只是想得出一个逆向的公式,就是并未显式指定的.c-inner的width实际的值应该等于.c2的width减去这个.c-inner显式指定的水平方向的padding、margin、border-width的值。那么.c-inner内部的宽度为100%的img会是多少你应该知道了吧?

此外,我们在.w-main的里面又套了一层.list,它的用处会在下面的CSS中体现:

.w-main{  margin: 30px auto;  background: #ddd;  overflow:hidden;
}.list{  margin: 0 -5px;
}.c2{  width: 50%;  float:left;
}.c3{  width: 33.333%;  float: left;
}.c4{  width: 25%;  float: left;
}.c-inner{  margin: 5px;
}.c-inner img{  vertical-align: middle;  width: 100%;
}

最终的Demo在这里,这是一个间距固定的多列布局,当然你可以发掘你的聪明才智让间距也变成百分比可缩放的。

刚才提到的.list我们只为其指定了margin: 0 -5px,注意此处并没有指定它的宽度,这样他的宽度才能通过这个负的margin各向左右扩展5px。这么做的目的是消除每行左右的5px间距,让图片贴边。因为设计师常常会在这种布局下让处于中间的那些列左右都留边距,而第一列和最后一列却是分别贴着容器的左右边的。此示例的完整代码你可以在JSBin上看到,你可以给.w-main加一条max-width: 1000px;的样式,就能更清晰地看到我所说的这种效果。

这种布局的优点显而见,就拿.c3来说,我们不需要通过只给中间那个.c3指定左右margin分别为10px这种办法来达到效果,想想如果按这种不合理的做法来布局,那么后端在重复输出多行.c3时,要识别序号为3n+2的.c3,可能要专门为他们加一个设置marign的类才能让它和左右的列保持距离。而用上面的布局后端在循环输出时不需要做任何多余的事。

3.2 背景图片

background-size是在响应式布局中背景图片上用得比较多的一个属性。background-size的默认值是auto,此时,背景图片不在乎容器的大小而是按图像的真实大小来显示;此外,你还可以用长度值和百分比来指定背景图片的大小;最后background-size还有两个预设值contain和cover,contain是将背景图片等比缩放到宽度或高度与容器的宽度或高度相等,背景图片始终被包含在容器内;而cover是将背景图片等比缩放到完全覆盖容器,背景图片有可能超出容器。下图是background-position为center时background-size不同取值的效果:

background-size

从上图中可以看到background-size的取值效果和img的width、height取值效果是一样的。当然,img没有contain和cover这样的值,针对上面这种情况,contain其实等同于background-size: 100% auto;cover等同background-size: auto 100%。是不是和img对width和height的取值完全合拍?但是,这里有个要注意的点,那就是原始图像比例和容器比例之间的关系,试想一下如果背景图像是一张纵向的图(高>宽),那么cover应该等同于background-size: 100% auto;contain等同background-size: auto 100%。如下图:
background-size
在响应式的实际开发中,最常用到的值是cover,目的是在任意尺寸下都能达到覆盖容器的效果。这样的话对这张背景图片的构图有些要求了,因为图片在真正显示的时候会被剪裁。为了让低版本浏览器也支持background-size: cover的效果,我写过一个插件,如果你感兴趣可以点击这里

3.3 视频的尺寸

视频?你就当它是图片来布局就好了,如下:

video{  width: 100%;  height: auto;
}

另外,我之前也写过一个视频的播放器Demo,根据video对象的API我们可以自定义控制条什么的,感兴趣你可以点击这里

3.4 padding占位

从上文“一、宽度百分比”中我们已经了解到margin、padding的单位为%时,它们是基于父级块的宽度的百分比,那么我们能用这一特性来做点什么呢?举个简单的例子:

padding

这是个两列布局,左侧图片的原始尺寸287 * 142(width * height),要求右侧绿色块与左侧图片同比缩放,注意右侧块中放的是文字,无法自动撑开与图片等高的高度。要达到上图的效果,我们可以通过设置右侧内部div的padding-top(或padding-bottom)为图片的height/width的百分比。此时要注意padding的百分比取值是相对于父级块的的宽度的,所以要保证父级块的宽度与左侧图片的父级块的宽度是一样的,这样取值才能正确。因为我们这个padding-top是设置给右侧这个与图片同宽的div的子元素的,所以它的父元素是下面代码中的.c2-padding,而不是.list。因此对于本例padding-top应该设置为142/287 = 49.477352%,而不是142/(287*2) = 24.738676%。让我们来看下html结构:

<!DOCTYPE html><html><head>
  <meta charset="utf-8">
  <title>padding占位</title></head><body><div class="w-main">
  <div class="list">
  <!--两列-->
    <div class="c2">
      <div class="c-inner"><img src="http://img0.bdstatic.com/img/p_w_picpath/shouye/systsy-11973574263.jpg" alt=""></div>
    </div>
    <div class="c2 c2-padding">
      <div class="c-inner">
        <div class="real-content">
          你好,我是文字,我的显示区域跟随左侧的比例        </div>
      </div>
    </div>
    <!--/两列-->
  </div></div></body></html>

下面样式表中重点是最后两组样式:

.w-main{  max-width: 1000px;  margin: 30px auto;  background: #ddd;  overflow:hidden;
}.c2{  width: 50%;  float:left;
}.c-inner img{  vertical-align: middle;  width: 100%;
}.c2-padding .c-inner{  position: relative;  padding-top: 49.477352%;  background: #73AA47;
}.c2-padding .c-inner .real-content{  position: absolute;  top: 0;  left: 0;
}

点击这里查看Demo。既然.c-inner是靠padding-top撑开的,那么其内部的.real-content就要用绝对定位脱离文档流,这样才能不受父级padding的影响正常显示。完整的代码在JSBin上。对于上面代码的实际应用可以看一下这个网站(sorry~这个站改版了,并且已经下线了,这里是已经改版过后的demo了),它在纯色背景的文字块上就用了这种布局思路。

除此之外,这种布局还可用在fadeIn/fadeOut效果的banner上,因为fade效果的banner需要让图片放在同一位置(left: 0; top: 0;),我们常用ul>li的结构,让li绝对定位,通过更改这些li的z-index同时对opacity做动画实现一种淡入淡出的banner效果。这样一来ul的高度因为内部li元素的绝对定位就无法撑起了,因此就可以用padding占位这个思路,这样的话JS只需要关心动画的逻辑而不需要去计算高度。这个网站的banner就是用这样的布局。

四、结尾

百分比的每块区域都会随着窗口的大小自动发生变化,有时我们也需要让某个块的尺寸是不变的,而其旁边的元素自动适应剩余宽度。我们将在下篇文章中对这种布局做进一步的探讨。



原文地址:http://www.codingserf.com/index.php/2014/08/responsive-and-mobile-2/