CSS进阶之路——BFC

300 阅读6分钟

简介

BFC全称Block Formatting Context——块级格式化上下文

它是一块独立的渲染区域,规定了在该区域内常规流块盒的布局方式

  • 常规流块盒在水平方向上,必须撑满包含块
  • 常规流块盒在包含块的垂直方向上依次摆放
  • 常规流块盒若外边距无缝相邻(没有borderpadding阻隔),则进行外边距合并
  • 常规流块盒的自动高度和摆放位置,无视浮动元素

什么条件下会创建BFC渲染区域?

这个区域由某个HTML元素创建,以下元素会在其内部创建BFC区域:

先列举一些比较常用的:

  • 根元素,意味着html元素创建的BFC区域

    • 几乎覆盖了网页中所有的元素,所以我们看到的常规流块盒基本都遵循上面所说的布局方式
  • 浮动(float属性不为none)元素
  • 绝对定位(position属性为absolutefixed)元素
  • 行内块元素(display属性为inline-block
  • 弹性元素(display属性为flexinline-flex的元素以及它们的直接子元素)
  • overflow不等于visible的块盒

下面是一些不常用的:

名称如何产生备注
表格元素表格元素默认display属性即是BFC表格单元格、表格标题也是
网格元素display属性为grid或inline-grid元素的直接子元素新特性用得少
多列容器元素的column-count或column-width不为auto,包括 column-count为1新特性用得少
display属性display属性值为flow-root的元素,flow-root: 一个新的display属性的值,它可以创建无副作用的BFC用得少
contain属性contain属性值为layout、content、paint的元素用得少

创建BFC区域的元素自身并不在该区域内

下图中的元素分别在谁创建的BFC区域内?

  • 根元素创建的BFC

    • 元素A、元素C、元素E、元素F、元素G
  • 元素A创建的BFC

    • 元素B
  • 元素C创建的BFC

    • 元素D
  • 元素G创建的BFC

    • 元素H

什么叫做独立的渲染区域?

不同的BFC区域,他们进行渲染时互不干扰,因为创建BFC的元素,隔绝了它内部和外部的联系,内部的渲染不会影响到外部。也就是说,创建了BFC 的元素可以看作是隔离了的独立容器,容器里面的元素在布局上不会影响到外面的元素,并且创建了BFC的容器具有普通容器所没有的一些特性,具体规则如下:

创建BFC的元素,它的自动高度需要计算浮动元素

<style>
  .father {
    background-color: yellow;
  }

  .son {
    float: left;
    width: 200px;
    height: 200px;
    margin: 20px;
    background-color: red;
  }
</style>

<body>
  <div class="father">
    <div class="son"></div>
    <div class="son"></div>
  </div>
</body>

由于父盒子是一个常规流块盒,根据常规流块盒的特性,计算自动高度时会无视浮动元素,最终计算出的高度为0,自然展示不出背景颜色,这种现象被称为高度坍塌

那么有哪些方法可以让父元素的高度被浮动的元素撑开呢?

  1. 普通清除浮动的方法
<style>
  .father {
    background-color: yellow;
  }

  .clearfix::after {
    content: '';
    display: block;
    clear: both;
  }

  .son {
    float: left;
    width: 200px;
    height: 200px;
    margin: 20px;
    background-color: red;
  }
</style>

<body>
  <div class="father clearfix">
    <div class="son"></div>
    <div class="son"></div>
  </div>
</body>
  1. 让父元素创建BFC

还有很多让父元素创建BFC的方式,我这里就演示一个最简单的方法,通过overflow: hidden属性创建,该方法同样也是所有创建BFC的方法中副作用最小的一种,并不会过度影响我们的布局,但只针对高度坍塌这个问题来讲,个人还是推荐普通清除浮动的方法

<style>
  .father {
    overflow: hidden;
    background-color: yellow;
  }

  .son {
    float: left;
    width: 200px;
    height: 200px;
    margin: 20px;
    background-color: red;
  }
</style>

从浏览器显示的结果可以看到父元素已经有了高度,黄色背景色也显示了出来

创建BFC的元素,不会与浮动元素重叠

<style>
  .son1 {
    float: left;
    width: 200px;
    height: 200px;
    background-color: red;
  }

  .son2 {
    height: 400px;
    background-color: #000;
  }
</style>

<body>
  <div class="father">
    <div class="son1"></div>
    <div class="son2"></div>
  </div>
</body>

由于son2是根元素下的常规流块盒子,其有个特点就是无视浮动元素,所以肯定会与浮动元素son1重叠

那么有哪些方法可以让浮动元素son1和常规流块盒子son2不重叠呢?

  1. 让son2创建BFC

我们还是采用最简单的创建BFC的方式:overvflow: hidden,由于创建BFC的元素具有不会和浮动元素重叠的特性,所以其会和son1分开。另外补充一点,创建了BFC后的son2依然是根元素BFC区域内的常规流块盒子,所以它一定会撑满包含块,但现在浮动元素占了一块位置,所以son2盒子的margin-left属性就会自动填满包含块

<style>
  .son1 {
    float: left;
    width: 200px;
    height: 200px;
    background-color: red;
  }

  .son2 {
    height: 400px;
    background-color: #000;
    overflow: hidden;
  }
</style>

<body>
  <div class="father">
    <div class="son1"></div>
    <div class="son2"></div>
  </div>
</body>

从显示结果来看,son2已经与浮动元素son1不再重叠,而且再去设置son2元素的margin-left值也不会生效,这种方法通常在布局中称为两栏布局

创建BFC的元素,不会和它的子元素进行外边距合并

<style>
  .father {
    width: 600px;
    height: 600px;
    margin-top: 10px;
    background-color: red;
  }

  .son {
    width: 300px;
    height: 300px;
    margin-top: 50px;
    background-color: #000;
  }
</style>

<body>
  <div class="father">
    <div class="son"></div>
  </div>
</body>

从下图中可以看出,父元素和子元素的顶部在同一水平线上,而且距离顶部距离为50px,并非10+50 = 60px,很明显产生了外边距折叠现象

那要怎么样才能消除外边距折叠,让子盒子距离父盒子顶部为50px,父盒子距离顶部10px呢?

  1. 为父元素加上透明的边框

常规流块盒中有个特点就是只有当外边距无缝相邻(没有borderpadding阻隔)的时候,才会进行外边距合并,所以我们只要使用border边框做一个隔离就可以让两个常规流块盒不产生边距合并

<style>
  .father {
    width: 600px;
    height: 600px;
    margin-top: 10px;
    background-color: red;
    border-top: 1px solid transparent;
  }

  .son {
    width: 300px;
    height: 300px;
    margin-top: 50px;
    background-color: #000;
  }
</style>

<body>
  <div class="father">
    <div class="son"></div>
  </div>
</body>

这样看样子效果是实现了,其实也有点瑕疵,因为border还是占了盒子位置的,多多少少对布局有一点影响

  1. 将父元素的margin值改为padding值

和第一种方法的原理一样,使用padding区域填充是不会产生边距合并的效果的

<style>
  .father {
    width: 600px;
    height: 600px;
    padding-top: 10px;
    background-color: red;
  }
  
  .son {
    width: 300px;
    height: 300px;
    margin-top: 50px;
    background-color: #000;
  }
</style>

<body>
  <div class="father">
    <div class="son"></div>
  </div>
</body>

这样子父元素顶部的空间就是由padding来撑起的,有个不足就是背景颜色是会包括我们的padding区域的,所以如果还有设置背景颜色的需求,建议使用其它方法

  1. 为父元素创建BFC

这里还是用我们最熟悉的overflow: hidden属性为元素创建BFC

<style>
  .father {
    width: 600px;
    height: 600px;
    margin-top: 10px;
    background-color: red;
    overflow: hidden;
  }

  .son {
    width: 300px;
    height: 300px;
    margin-top: 50px;
    background-color: #000;
  }
</style>

<body>
  <div class="father">
    <div class="son"></div>
  </div>
</body>

因为创建了BFC的元素是不会和它的子元素发生外边距合并的,最终我们就看到了希望的效果

这个规则还可以这样解释:处在不同BFC区域的元素,他们的外边距不会合并,只有在同一个BFC区域内,他们的外边距才会合并

因为当父元素创建了BFC后,son元素处在father元素的所创建的BFC区域内,但father元素其实是在根元素所创建的BFC区域内的,所以子元素和父元素所在的BFC区域不同,它们的外边距自然是不会合并的