05 September 2013

关于clearfix清除浮动等导致父容器高度塌陷的细节

因为项目常常要求快速开发,Bootstrap前端框架用的比较多,里面写好了很多class,可以直接拿来用,很方便。

而其中有一个类:.clearfix ,是使用最多的类之一,它可以用来清除由于内部元素float导致的父容器高度塌陷问题。

我们先来看看它的写法:

.clearfix {
  *zoom: 1;
}

.clearfix:before,
.clearfix:after {
  display: table;
  line-height: 0;
  content: "";
}

.clearfix:after {
  clear: both;
}

这种写法,常叫做“micro clearfix hack”,效果我们试试都知道,并没有问题,但是对这其中的原理我想很多人可能比较迷惑,比如说为毛要用 display:table

参阅Nicolas Gallagher的一篇文章《A new micro clearfix hack》,文章中解释了为什么使用 :before ,为什么使用 display:table ,等等。

别小看这个 .clearfix ,看起来就这么几行,涉及的东西还真不少。

我同时打开了Chrome,FF,Opera、三个虚拟机分别跑IE6、IE7、IE8 ,在这6个浏览器下同时反复测试后,终于搞清楚了每一行的意思。

首先,html如下:

<style>
.container {
    background: yellow;
    margin: 40px 0px;
}

.left {
    width: 30px;
    height: 100px;
    background: blue;
    float: left;
    margin: 50px 0px;
}
</style>
<div class="wrapper">
    <div class="container clearfix">
        <div class="left ">
            abababasdfjlsjdflkjlsk
        </div>
    </div>
</div>

1.关于使用display:table还是display:block;

首先,原文中的意思,用 display:table 的原因是为了避免外边距margin重叠导致的margin塌陷。

好吧,我测来测去,都看不出所谓的margin塌陷在哪啊…

最后又查了下资料 ,发现所谓的margin塌陷是发生在普通流上的,而且要满足几个条件(汗…基础又不扎实了):

  • 两个或多个元素之间的margin相互作用;
  • 毗邻,指两元素之间没有被非空元素分隔开(如padding);
  • 垂直方向,只有margin-top,margin-bottom会相互作用;
  • 普通流(既文档流)。float不是普通流,position:absolute也不是普通流。

好吧,我的测试元素 float:left ,当然不会塌陷啦。

ok,将里层元素的 float:left 去除,将 .clearfix 中的display改为block,好吧,看到塌陷了。

这里还要注意!这个情况每个浏览器的表现还不一样。

Chrome,Opera和FF,IE9,IE10等较新的浏览其中,使用 display:block 时,上下的margin都会被重叠部分吃掉。

而IE8表现为只有上margin被吃掉,而IE6、7因为不支持伪元素,所以改变display对其并无影响。

好了,明白为什么用 display:table 后,让我们来看看到底 display:table 是何方神圣。

display:table的作用是,让元素展示成像一个table。

在这里它的作用是在两个伪元素中分别自动生成一个匿名表达单元(anonymous table-cell),并将这一行展示为一个块级元素。

ok,回头看margin塌陷的第二个条件,两元素间需要没有被非空元素分隔。

在这里,由于display:table,在上下伪元素中生成了匿名table-cell,它分隔了两个元素(抽象上),成功的阻止了margin塌陷。(个人理解)

2.IE下的一些问题

首先当然要说的是IE6、7两个老古董,由于它们不认识伪元素是什么东东,所以上面一大串都与它俩无关。

影响它们的是那一行 hack: *zoom:1; 这里又要扯到另外一个概念layout了,详细可以参阅:关于ie的layout深层剖析

简而言之,就是layout是ie下的专有属性,它由每个元素内部的hasLayout属性决定,一个元素是否具有layout可能会引起一系列问题,甚至影响它们的子元素,可能引起的问题有:

  1. IE很多常见的浮动 bug 。
  2. 元素本身对一些基本属性的异常处理问题。
  3. 容器和其子孙之间的边距重叠(margin collapsing)问题。
  4. 使用列表时遇到的诸多问题。
  5. 背景图像的定位偏差问题。
  6. 使用脚本时遇到的浏览器之间处理不一致的问题。

ok,不扯远。看到1,3条,binggo!正是我们clearfix要解决的问题,那么怎么才能使元素具有layout呢?

很简单,zoom:1 即可。

说到这里,另外的问题又来了。

当元素不是浮动元素时,显示没有问题,上下margin都正常,但是一旦元素 float:left , “垮嗒”一声,margin-bottom塌了!

这又是什么情况?

各种查之后得知,这是ie6、7下的一个bug:当元素float以后,margin-bottom会失效。

而css解决的办法竟然是…木有!

唯一让它显示margin-bottom的办法就是在float元素之后添加一个空元素,并设置clear:both;

或者直接不实用margin-bottom,而改用父元素的padding-bottom。

或者你可以将 *zoom:1这句改为

.clearfix {
    *zoom: expression(this.runtimeStyle.zoom="1",this.appendChild(document.createElement("br")).style.cssText="clear:both;font:0/0 serif");
}

但是考虑到css表达式效率低的问题,这个办法谨慎使用。

3.最后…在测试时发现的一个小问题。

就是我的测试html中的子元素是原本定高定宽的。

在所有浏览器中表现出来的都是一个正方形,文字超出子元素,唯有IE6,它会随着子元素中的字符长度而边长!

这一点很匪夷所思。。设置 overflow:hidden 后消失。

唉,今天先不研究这个问题了,记住有这么一个奇怪现象就好了。

Posted in 2013-09-05 18:49