想让一个区域富有弹性,并非只有一种办法。首先来看一下我们想要实现的效果:

bfc

显而易见,这样的布局需求是头像不动消息动。换句话说,就是两个头像始终贴边,而中间的消息就像有弹性一样随着不同的设备分辨率(窗口)自动伸缩。

一、BFC图文混排

要实现这样的布局首先要理解一个概念BFC——块级格式化上下文(Block Formatting Context)。

BFC是个独立渲染区域,它内部元素的布局与外部毫不相干,并且所有的BFC都遵守同一套布局规则。比如,同一个BFC中上下两个相邻的Box在垂直方向的margin会发生重叠(或叫作塌陷);浮动和清除浮动这件事只对同一个BFC中的元素起作用;计算BFC的高度时,浮动元素也参与计算;最后,也是我们今天主要用到的一条BFC规则:BFC的区域不会与浮动Box重叠

那么我们如何触发一个BFC呢?只需设置元素下列CSS属性之一就行:

  • float:left | right
  • position:absolute | fixed
  • display:  inline-block | table-cell | table-caption | flex | inline-flex
  • overflow:  hidden | scroll | auto

现在你应该知道我们为什么能用overflow:hidden;来清除浮动了吧。

了解了BFC之后,我们回过头来看看实现上图布局所用到的html结构:

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <title>BFC image and text layout</title>
</head>
<body>
<div class="msn-item msn-item_other">
  <div class="msn-head"><img src="http://img0.bdstatic.com/img/image/shouye/sjsh-9626448682.jpg" alt=""></div>
  <p class="msn-content">
    ……
 </p>
</div>
<div class="msn-item msn-item_me">
  <div class="msn-head"><img src="http://img0.bdstatic.com/img/image/shouye/qcmt-9567004004.jpg" alt=""></div>
  <p class="msn-content">
    ……
  </p>
</div>
</body>
</html>

代码结构很简单,每条消息(.msn-item)里包含着一个头像(.msn-head)和一条消息文本(.msn-content)。下面是精简的主要样式:

.msn-head{
  width: 60px;
  height: 60px;
}
.msn-item_other .msn-head{
  float: left; /* 敌人头像靠左 */}
.msn-item_me .msn-head{
  float: right; /* 我军头像靠右 */
}
.msn-content{
  overflow:hidden; /* 触发BFC */
  word-wrap: break-word; 
  word-break: normal; 
}

这里是Demo。因为BFC内部元素的左边都会紧贴这个BFC的左边框(对于从左往右的格式化,否则相反。这也是BFC规则之一),并且BFC的区域又不会与浮动元素重叠,因此,触发了BFC的.msn-content,会根据父级元素的宽度和相邻的浮动元素的宽度(包括浮动元素的margin)自动变窄。这里是JSBin上的完整代码。

图中的布局需求是一边不变另一边自动伸缩,我们可以称其为“头不变,身子变”,此外,我们还可能有一种需求是“头尾不变,中间变”,即两头是不变的,中间伸缩。如下图:

头尾不变,中间变

对于这样的布局首先要注意下结构:

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <title>头尾不变中间变</title>
</head>
<body>
<div class="item">
    <div class="item-head">我不变</div>
    <div class="item-tail">我也不变</div><!--我是尾,但是我放在身子的前面-->
    <p class="item-content">拖动窗口看我72变!拖动窗口看我72变!拖动窗口看我72变!拖动窗口看我72变!拖动窗口看我72变!拖动窗口看我72变!拖动窗口看我72变!拖动窗口看我72变!拖动窗口看我72变!拖动窗口看我72变!拖动窗口看我72变!拖动窗口看我72变!</p>
</div>
</body>
</html>

我们看到HTML结构中我着重说明了一点,就是.item-tail是放在.item-content的前面的。不急,我们先来看css:

.item-head,.item-tail{
  height: 50px;
  line-height: 50px;
  text-align: center;
}
.item-head{
  float: left;
  width: 60px;/* 你可以删除width看看效果 */
  background:#E99528;
}
.item-tail{
  float: right;
  width: 100px;/* 你可以删除width看看效果 */
  background:#317F97;
}
.item-content{
  overflow: hidden; /* 触发BFC */
  height: 50px;
  
  white-space:nowrap; 
  text-overflow:ellipsis; 
  -o-text-overflow:ellipsis; 
  
  line-height: 50px;
  background: #73AA47;
}

这里是Demo。看了上面的CSS代码,我们知道.item-head向左浮动,.item-tail向右浮动。我们把.item-content放到这两个浮动元素的后面,并且让其触发BFC。这么做才能让.item-content夹在两个浮动box中间,且不与他们重叠。这都是遵循BFC规则的。JSBin上有完整的代码。此外,对于浮动的理解,Eric Meyer曾在他的《CSS2.0 Programmer’s Reference》(虽然它出版于2006年,但CSS的原理一直没变)中说过这样一段话:“当你浮动一个元素的时候……这些(浮动)规则就好像在说:‘尽量把这个元素往上放,能放多高放多高,直到碰到某个元素的边界为止。’”

其实,实现文章开头那张聊天界面的布局并非只有一种方法,我们这里简单的提一下另外两种布局。

1.绝对定位图文混排

先看下结构:

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <title>absolute image and text layout</title>
</head>
<body>
<div class="msn-item msn-item_other">
  <div class="msn-head"><img src="http://img0.bdstatic.com/img/image/shouye/sjsh-9626448682.jpg" alt=""></div>
  <div class="w-msn-content">
    <p class="msn-content">
      ……
    </p>
  </div>
</div>
<div class="msn-item msn-item_me">
  <div class="msn-head"><img src="http://img0.bdstatic.com/img/image/shouye/qcmt-9567004004.jpg" alt=""></div>
  <div class="w-msn-content">
    <p class="msn-content">
      ……
    </p>
  </div>
</div>
</body>
</html>

上面代码的结构与之前BFC的结构唯一的区别就是在.msn-content的外面加了一层.w-msn-content。其目的是想通过给.w-msn-content加padding(敌人在左,我军在右)来空出头像.msn-head的固定宽度,然后,让.msn-head绝对定位到这个空出来的padding区域。那么根据我们在“响应式与移动端(二)”里对宽度继承的理解,.msn-content的宽度是父级容器.w-msn-content宽度减去padding后的值,因此,.msn-content会随着父级容器伸缩。我们来看下主要的CSS代码:

.msn-head{
  position: absolute;/*绝对定位*/
  width: 60px;
  height: 60px;
}
.msn-item_other .msn-head{
  left: 15px; /* 敌人头像靠左 */
}
.msn-item_me .msn-head{
  right: 15px; /* 我军头像靠右 */
}

/* 固定padding,分别空出左右头像位置 */
.msn-item_other .w-msn-content{
  padding-left: 75px;
}
.msn-item_me .w-msn-content{
  padding-right: 75px;
}
.msn-content{
  word-wrap: break-word; 
  word-break: normal; 
}

你可以看下Demo,完整的代码和最终的效果可以在JSBin上查看。

2.表格图文混排

在表格布局被历史尘封的今天,搬出它来似乎只能做为反面教材。然而,在我不谙世事的时候确实有用过table来解决这种“伸缩”的需求。它的缺点是结构死板,不能换行。BFC和绝对定位的方式可以在某个尺寸下结合media queries,让头像和文字处于两行,产生灵活的布局,而table却无能为力。不过它也并非一无是处,此处也姑且可用。这里是Demo,具体的代码请看JSBin

二、Flex图文混排

一个标准在三年内变了三次,市场一片混乱。

此处我不想花太多的精力介绍弹性布局相关属性,关于弹性布局网上的文章很多,这里有一篇《A Complete Guide to FlexBox》推荐给大家,w3cplus上有中文版译文。你可能需要花点时间来理解主轴侧轴、flex-grow、flex-shrink、flex-basis等等。

对于文章开头的布局而言,如果我们使用弹性盒模型(FlexBox)来实现的话,在html结构上不需要做任何改变,可以完全和BFC的结构一样。那么CSS就是关键了,来看下代码(希望你不会头大):

.msn-item{
    /* 声明父级容器为伸缩容器 */
    display: -webkit-box;
    display: -moz-box;
    display: -ms-flexbox;
    display: -webkit-flex;
    display: flex;
    align-items: flex-start;
}
.msn-item_me{
    /* 伸缩项目的排列顺序反转,让头像靠右排 */
    -webkit-box-direction: reverse;
    -moz-box-direction: reverse;
    -webkit-flex-direction: row-reverse;
    -ms-flex-direction: row-reverse;
    flex-direction: row-reverse;
}
.msn-head{
  width: 60px;
  height: 60px;
}
.msn-content{
    /* 让文字内容占满容器剩余空间 */
    -webkit-box-flex: 1;
    -moz-box-flex: 1;
    -webkit-flex: 1 1 0;
    -ms-flex: 1 1 0;
    flex: 1 1 0;
    /* 让文字延侧轴自动对齐 */
    -webkit-align-self: auto;
    -ms-flex-item-align: auto;
    align-self: auto;
  
  word-wrap: break-word; 
  word-break: normal; 
}

Demo在此。完整的代码你可以看JSBin。如果你想了解浏览器对于弹性盒模型的支持程度,请看Can I Use。Caniuse.com是我很常用的一个网站,建议大家收藏,我常常在上面查看HTML5、CSS3、JS最新API在不同浏览器中支持情况。

因为目前市场上的现代浏览器对弹性盒模型支持的是不同年代的标准,并且同一条属性在不同浏览器里的默认值还是不一样的,因此为了兼容导致我们要写这么多不同版本的代码。你可以NB到每个版本都记住,也可以用这个工具来生成各个版本都兼容的代码。

如果不是为了实现更复杂的布局,而只是像本文这种简单的图文混排,我宁愿选择BFC的方式。

三、最后

无论是之前讨论的百分比,还是本文中的BFC,都是CSS原理上的东西,只要对CSS原理足够了解,就可以轻松驾驭自适应的布局。对于CSS原理,一本不可多得的好书就是Eric Meyer的《CSS 权威指南》。

 

 


响应式与移动端系列:
响应式与移动端(一):万事俱备了吗?
响应式与移动端(二):了不起的百分比
响应式与移动端(三):弹力十足的图文混排
响应式与移动端(四):会变脸的媒体查询
响应式与移动端(五):交互的过渡