# CSS3教程 - 8 高度塌陷与BFC

浮动本来是用来文字环绕图片的,不是用来布局的,但是后来逐渐被用来横向布局,这就好比伟哥的发明本来是为了研发治疗心血管疾病的药物,结果下面挺起来了...

但是浮动存在一些问题,下面就来介绍一下。

# 8.1 高度塌陷问题

HTML 页面布局中,块级元素的高度默认是由其子元素内容撑开的。

举个栗子:

<!DOCTYPE html>
<html>
  <head>
    <style>
      .outer {
        border: 5px red solid;
      }

      .inner {
        width: 100px;
        height: 100px;
        background-color: lightskyblue;
      }

      .other {
        width: 150px;
        height: 150px;
        background-color: lightpink;
      }
    </style>
  </head>

  <body>
    <div class="outer">
      <div class="inner"></div>
    </div>
    <div class="other"></div>
  </body>
</html>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
  • outer 没有设置高度,默认被 inner 撑开。

显示如下:

但是当设置 inner 浮动后,子元素会脱离文档流,将会无法撑开父元素,导致父元素高度没了。

演示一下,修改子元素为浮动:

.inner {
  width: 100px;
  height: 100px;
  background-color: lightskyblue;
  float: left;  /* 设置浮动 */
}
1
2
3
4
5
6

显示如下:

可以看到父元素高度没了,也就是父元素高度塌陷,此时如果父元素下面还有别的页面布局,将会造成页面布局混乱。

所以这个问题必须要解决!

# 8.2 BFC

# 1 BFC简介

BFC(Block Formatting Context,块级格式化上下文)是一种 独立的渲染区域,其中的元素布局不会受到外部元素的影响,同时会按照独立的规则处理其内部子元素。

BFC 是 CSS 中的一个隐性特性。我们可以通过特定的方式为元素触发 BFC。一旦开启 BFC,该元素便成为一个独立的布局区域,能够有效解决浮动清除、高度塌陷、外边距合并等常见布局问题。


元素开启 BFC 后的特点

  1. 开启BFC的元素,不会被浮动元素覆盖;

  2. 开启BFC的元素,父子元素外边距不会重叠;

  3. 可以包含浮动元素,避免高度塌陷。

这里主要使用上面 BFC 的第三个特点,解决父元素高度塌陷。

# 2 开启BFC

元素如何开启BFC?BFC不是指定的属性,但是可以通过一些方式来触发 BFC,下面介绍一些常用的方式。


  1. 设置元素为浮动

设置父元素为浮动,父元素开启BFC。

.outer {
  border: 5px red solid;
  float: left;  /* 设置浮动 */
}
1
2
3
4

显示如下:

此时父元素确实包裹住子元素了,但是父元素的宽度是没有了,因为也脱离文档流了。父元素后面的元素上移了,被父元素挡住了,如果想让父元素后面的元素不被父元素挡住,那么让后面的元素也浮动就可以了。

纳尼?那不是整个页面元素都要浮动?开启 BFC 都会有一些副作用,我们要寻找副作用少一些的开启方式。所以这种方式不推荐。


  1. 将元素设置为行内块元素

设置父元素为行内块元素。

.outer {
  border: 5px red solid;
  display: inline-block;
}
1
2
3
4

显示如下:

父元素不再独占一行(上面粉色的元素是块元素,所以看不出来),宽度也没有了,同时与下方元素会有一点间隙,不推荐。


  1. overflow 设置为除 visible 以外的值

常用的方式为元素设置父元素 overflow:hidden ,设置为 overflow:auto 也可以,overflow:scroll 也可以,但是会有滚动条,不太推荐。

.outer {
  border: 5px red solid;
  overflow: hidden;   /* 清除浮动 */
}
1
2
3
4

显示如下:

可以看到父元素独占一行,宽度正常,高度也没有塌陷。这种方式副作用比较小,但是还是有副作用。

举个例子:

<!DOCTYPE html>
<html>
  <head>
    <style>
      .div1 {
        width: 100px;
        height: 100px;
        background-color: lightpink;
        float: left;
      }

      .div2 {
        width: 150px;
        height: 150px;
        background-color: lightblue;
        overflow: hidden; 
      }
    </style>
  </head>

  <body>
    <div class="div1"></div>
    <div class="div2"></div>
  </body>
</html>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
  • 上面两个 div, 将 div1 设置为浮动后,div2overflowhidden

显示如下:

可以看到 div2 没有排列在 div1 的下面,而是排列到 div1 右边了, overflow 并没有完全清除 div2 在布局上受到的影响。

所以 BFC 多多少少会有一些副作用。


# 8.3 clear清除浮动

首先看一下下面的代码:

<!DOCTYPE html>
<html>
  <head>
    <style>
      .div1 {
        width: 100px;
        height: 100px;
        background-color: lightpink;
        float: left;
      }

      .div2 {
        width: 150px;
        height: 150px;
        background-color: lightblue;
        float: right;
      }

      .div3 {
        width: 100px;
        height: 100px;
        background-color: lightgreen;
      }
    </style>
  </head>

  <body>
    <div class="div1">1</div>
    <div class="div2">2</div>
    <div class="div3">3</div>
  </body>
</html>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
  • 3个 div,div1 左浮动, div2 右浮动,所以 div3 应该被顶上去,被 div1 挡住。

显示如下:

与预想的一致,只是浮动后,文字会环绕,所以 div1 挡住了 div3,但是挡不住文字。


我们可以使用 clear 属性,清除浮动对当前元素产生的影响。

clear 的可选值:

  • left:清除左侧的浮动,使当前元素的上边缘位于左侧浮动元素的下方;
  • right:清除右侧的浮动,使当前元素的上边缘位于右侧浮动元素的下方;
  • both:清除两侧中最大影响的浮动,不是同时清除两侧的影响。

举个栗子:

在上面代码的基础上进行。

# 1 清除左侧浮动

设置 div3 清除左侧浮动:

.div3 {
  width: 100px;
  height: 100px;
  background-color: lightgreen;
  clear: left;  /* 清除左侧的浮动 */
}
1
2
3
4
5
6

显示如下:

可以看到,div3 清除了左侧的浮动产生的影响,div3 将位于左侧浮动元素的下方。需要注意,此时 div1 还是浮动的。


# 2 清除右侧浮动

设置 div3 清除右侧浮动:

.div3 {
  width: 100px;
  height: 100px;
  background-color: lightgreen;
  clear: right;  /* 清除左侧的浮动 */
}
1
2
3
4
5
6
  • div3 清除了右侧的浮动,div3 将位于右侧元素的下方。

显示如下:

# 3 清除影响较大浮动

设置 div3 清除两侧影响较大的浮动:

.div3 {
  width: 100px;
  height: 100px;
  background-color: lightgreen;
  clear: both;  /* 清除左侧的浮动 */
}
1
2
3
4
5
6

显示如下:

div3 清除两侧影响较大的浮动,和上面清除右侧浮动是一样的,因为 div 产生的影响更大。

如果将 div2 的尺寸改小,那么就是清除 div1 产生的浮动。

显示如下:

# 4 高度塌陷比较完美方案

前面说了使用 BFC 将 overflowhidden 的方式仍然存在副作用。

那么使用 clear 清除浮动的方式来解决高度塌陷呢?

先看一下下面的代码:

<!DOCTYPE html>
<html>
  <head>
    <style>
      .outer {
        border: 5px red solid;
      }

      .inner1 {
        width: 100px;
        height: 100px;
        background-color: lightskyblue;
        float: left;
      }

      .inner2 {
        width: 120px;
        height: 120px;
        background-color: lightgrey;
      }

      .other {
        width: 150px;
        height: 150px;
        background-color: lightpink;
      }
    </style>
  </head>

  <body>
    <div class="outer">
      <div class="inner1"></div>
      <div class="inner2"></div>
    </div>
    <div class="other"></div>
  </body>
</html>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
  • 在上面的代码中,inner1 左浮动,脱离了文档流,inner2 没有浮动,所以 inner2(灰色) 被 inner1(蓝色) 挡住了。

显示如下:

但是此时 outer 的高度没有塌陷,因为被 inner2 撑开了。

此时如果设置 inner2 清除浮动呢?

.inner2 {
  width: 120px;
  height: 120px;
  background-color: lightgrey;
  clear: both;  /* 清除浮动 */
}
1
2
3
4
5
6

显示如下:

可以看到 inner2 没有受到 inner1 浮动的影响,同时 outer 被撑开了,高度没有塌陷。

此时 inner2 是设置了高度的,如果不设置 inner2 的高度呢?

.inner2 {
  clear: both;  /* 清除浮动 */
}
1
2
3

显示如下:

牛逼,可以看到显示正常了,是我们想要的效果!

所以要解决父元素因为子元素浮动导致的塌陷问题,可以在父元素最后添加一个空的块元素,一般是div,并清除浮动。

这是一个比较完美的方案,为什么说比较完美呢,因为需要凭空添加一个没有用的元素。

下面继续进化...


# 8.4 after完美解决高度塌陷

上面解决高度塌陷,需要额外添加一个元素,需要通过结构来改变视觉,不够优雅,样式的问题应该通过 CSS 来实现。

那么如何更优雅呢?

我们可以使用伪元素来实现,在父元素的后面添加一个伪元素来实现。(伪元素在讲解选择器的时候说过)

代码如下:

<!DOCTYPE html>
<html>
  <head>
    <style>
      .outer {
        border: 5px red solid;
      }

      .inner {
        width: 100px;
        height: 100px;
        background-color: lightskyblue;
        float: left;
      }

      .outer::after {  /* 添加伪元素 */
        content: "hello";
        display: block;  /* 设置为块元素 */
        clear: both;  /* 清除浮动 */
      }

      .other {
        width: 150px;
        height: 150px;
        background-color: lightpink;
      }
    </style>
  </head>

  <body>
    <div class="outer">
      <div class="inner"></div>
    </div>
    <div class="other"></div>
  </body>
</html>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
  • 在上面的代码中,使用 .outer::after 在父元素的后面添加一个伪元素,伪元素默认是行内元素,这里需要将其设置为块元素才可以,同时清除浮动。

显示如下:

可以看到效果差不多了,已经解决了塌陷,将伪元素的内容去掉就可以了:

.outer::after {
  content: "";
  display: block;
  clear: both;
}
1
2
3
4
5

显示如下:

完美!!!


# 8.5 clearfix完美解决边距折叠和高度塌陷

前面在讲解盒子模型的时候,父子元素的边距折叠问题,当时是通过padding和border来隔开父元素和子元素,没有完美的解决。

下面就来介绍如何完美的解决这个问题。

回顾一下问题:

<!DOCTYPE html>
<html>
  <head>
    <style>
      .box1 {
        width: 200px;
        height: 200px;
        background-color: blue;
      }

      .box2 {
        width: 100px;
        height: 100px;
        background-color: red;
        /* 设置上外边距 */
        margin-top: 100px;
      }
    </style>
  </head>

  <body>
    <div class="box1">
      <div class="box2"></div>
    </div>
  </body>
</html>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
  • 父 div 包含子 div,子 div 设置了上边距。

想要的效果:

实际效果如下:

子元素设置了一个 margin-top 之后,父元素跟随子元素一起进行了移动,出现了垂直方向上的父子边距的折叠问题。

如何完美解决?

聪明如我,使用伪元素来解决,我就不卖关子了,直接上代码:

.box1::before {
  content: '';
  display: table;  /* 这个地方,值需要设置为table */
}
1
2
3
4
  • 给 box1 开始位置添加一个伪元素,将父元素和子元素隔开,但是需要注意,display 的值需要设置为 table,table不占用空间,还能隔开,block隔不开。

这样就完美解决了。


为了同时解决父元素高度塌陷和垂直方向上的父子元素边距折叠问题,有人提出了一个比较好的方案,编写一个样式同时解决了这两个问题:
/** 解决高度塌陷和父子垂直外边距重叠问题 */
.clearfix::before,
.clearfix::after {
  content: "";
  display: table;
  clear: both;
}
1
2
3
4
5
6
7
  • 上面的 content 和 display 解决外边距折叠问题;display 和 clear 解决高度塌陷问题(table也可以清除浮动)。

这样在需要的地方,直接使用 clearfix 的类即可。

就像上面的代码,直接修改如下:

<!DOCTYPE html>
<html>
  <head>
    <style>
      .box1 {
        width: 200px;
        height: 200px;
        background-color: blue;
      }

      /** 解决高度塌陷和父子垂直外边距重叠问题 */
      .clearfix::before,
      .clearfix::after {
        content: "";
        display: table;
        clear: both;
      }

      .box2 {
        width: 100px;
        height: 100px;
        background-color: red;
        /* 设置上外边距 */
        margin-top: 100px;
      }
    </style>
  </head>

  <body>
    <div class="box1 clearfix">
      <div class="box2"></div>
    </div>
  </body>
</html>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
  • 给外面的 div 直接添加 clearfix 类即可。

完美解决两大恶心问题!