【CSS】让不进脑子的BFC知识点强行进脑

1,166 阅读9分钟

【祖传开头】

早睡早起保平安

一、BFC概念

BFC即Block Formatting Contexts,块级格式上下文。这个概念听起来就是非常不容易进脑子,所以简单拆分一下关键字分别进行理解一下。

1.1 关键字拆分

块级:顾名思义就是块级元素,比如最常见的div,p等块级标签。

格式化 :通过一个非常简单的例子来说明一下什么是格式化,以下是一段JSON 代码。

{"a": 1, "b": 2, "c": { "d": 3 }}

在进行格式化后:

{
    "a": 1,
    "b": 2,
    "c": {
        "d": 3
    }
}

可以看到JSON代码的一些显示变化:

  • 每个键值对单行显示
  • 每个层级的缩进需要4个空格
  • 冒号后需要一个空格
  • ...

代码通过运用这些变化规则,使代码变得更加美观、规整,看起来更加舒服。这就是所谓的格式化。
所以对于格式化的定义,可以简单的概括为根据制定的具体规则,对内容展示做一些处理,从而达成某些规范,或者 使内容更加美观合理。

这个过程就是格式化

上下文 :确定格式化的范围。比如上述例子,就是只需要对这一段代码进行格式化,它可能是一个单独的json文件或是单独选中的代码块。

进行关键字拆分理解后,对于这个概念块级格式上下文,就可以大致理解了。在正常的布局流中:

  • 在满足一定条件下,一个普普通通的块级元素,就会变成一个块级格式上下文。
  • 在这个上下文范围内,所有的元素排列都会遵循一定的规则,即格式化的规则,进行排列布局。(且针对不同类型的元素会对应不同的规则)。
  • 这个范围内的元素不管如何布局都不会影响到上下文外部的元素,也就是一个独立的布局单位

1.2 如何创建BFC

上述说到一个普通的块级元素并不是一个真正的BFC,而实要满足一定的条件,比如以下条件就会让一个普普通通的div或者p标签变成一个BFC。

以下方式参考MDN文档

  • 根元素(html)
  • 浮动元素(元素的 float 不是 none)
  • 绝对定位元素(元素的 position 为 absolute 或 fixed)
  • 行内块元素(元素的 display 为 inline-block)
  • 表格单元格(元素的 display 为 table-cell,HTML表格单元格默认为该值)
  • 表格标题(元素的 display 为 table-caption,HTML表格标题默认为该值)
  • 匿名表格单元格元素(元素的 display 为 table、table-row、 table-row-group、table-header-group、table-footer-group(分别是HTML table、row、tbody、thead、tfoot 的默认属性)或 inline-table)
  • overflow 计算值(Computed)不为 visible 的块元素
  • display 值为 flow-root 的元素
  • contain 值为 layout、content 或 paint 的元素
  • 弹性元素(display 为 flex 或 inline-flex 元素的直接子元素)
  • 网格元素(display 为 grid 或 inline-grid 元素的直接子元素)
  • 多列容器(元素的 column-count 或 column-width 不为 auto,包括 column-count 为 1)
  • column-span 为 all 的元素始终会创建一个新的BFC,即使该元素没有包裹在一个多列容器中

1.3 🌰举个例子

上述说到页面中的根元素html就是一个BFC,所以我们的html文档会遵从默认的布局规则。

  • 文本从左到右、从上到下进行排列
  • 块级元素默认占据一行,即块元素尾部带有一个换行符
  • 行元素的尾部不带有换行符
  • 行内元素需要包含在块级元素中
  • ... 实例代码:
<html>
  <body>
    <div id="block1">
      <span>文本1</span>
      <span>文本2</span>
    </div>
    <div id="block2">
      <p>文本3</p>
      <p>文本4</p>
    </div>
  </body>
</html>

image.png

可以看到元素的展示都按照默认的规则进行排列了,span标签不换行,p标签进行换行。

此刻,我们给div#block2,进行升级,将它转化为一个BFC,比如参照上述1.2中的方式:

#block2 {
  dispaly: flex;
}

image.png

我们发现P标签不能换行了。所以在这个小小的BFC(即div#block2)中,它改变了这个范围内元素的布局规则,我们可以将这个div看做是一个独立的布局范围。

所以,综上所述。根元素html是一个最顶级的块级格式上下文,向下对文档中的元素或者对其他的块级作用域进行格式化管理。而每个BFC也可以制定自己的规则,向下去制定子元素的布局规则。

二、BFC作用

2.1 阻止外边距(margin)重叠

在正常布局流中有一个比较重要的规则,就是在同一个BFC中,块级元素的垂直外边距会进行合并。

情景一: 相邻元素的合并

<div class="block"></div>
<div class="block"></div>
.block {
  margin: 10px;
}

image.png

两个div之间的距离仍旧是10px,并不是20px。这个现象就是垂直外边距合并的现象。

情景二: 父元素未设置border和padding时,子元素的margin溢出。

当然这种场景,还可以为父元素设置border和padding,来防止子元素margin的溢出。

<div class="block"></div>
<div class="box">
    <div class="block"></div>
</div>
.block {
  margin: 10px;
}

image.png

那么当不想要外边距合并时,可以通过给相邻元素的其中之一,进行一层BFC隔离封装
因为上述说到BFC本质上一个独立的布局单位,这个BFC范围中的元素会被完全包含在这个范围内,包括元素的外边距。
将情景二中的父元素div.box升级为一个BFC容器,此处通过overflow:hidden。可以发现margin在垂直方向不再进行合并。

<div class="block"></div>
<div class="bfc-box">
    <div class="block"></div>
</div>
.bfc-box { 
  overflow: hidden;
}

image.png

2.2 清除浮动

首先先来说一下浮动元素。当元素的设置CSS属性float设置为left或者right,这个元素就会脱离正常流。但是浮动元素并不会完全消除在正常布局流中影响。比如以下例子:

<div id="div">
  <div class="fl">float div</div>
    <div class="fr">float right</div>
  <div class="content">content</div>
</div>
#div {
  width: 500px;
  padding: 20px;
  border: 1px solid #ffb366;
}
.fl {
  float: left;
}
.fr {
  float: right;
}

image.png

上述两个div分别向左和向右进行浮动,虽然脱离了文档流,但是还是在#div中会占据一定的空间。这个其实是一种CSS防止元素重叠的一种机制——尽可能避免元素重叠。所以可以了解到浮动元素在一定程度上会对正常元素的显示空间造成一定的影响。

了解完了浮动元素的基本外在表现后,再来了解浮动元素的其他显示规则:

  • 浮动范围不会超过其父元素的内边距边界。
  • 浮动元素会尽可能向上排列,即从父元素的最上方开始排列。
  • 按照元素的先后的顺序进行排列。
  • 浮动元素在不设置定量宽度时,虽然是块级元素,但是其默认宽度遵循max-content的显示规则,即宽度会尽量缩小。

上述例子中我们可以看到div的排列顺序依次是div.fldiv.frdiv.content

  1. 排列div.fl,放到了父容器的左上边界。
  2. 排列div.fr,放到了父容器右上边界。
  3. 排列div.content,发现div.fl和div.fr之间的空间可以放的下content,放置在两者之间。

了解完浮动的排列规则后,继续来了解清除浮动。
清除浮动:就是将正常流中的元素两侧的浮动元素清除,清除浮动又可以分为清除左边和清除右边浮动。
比如上述例子中,将content元素设置清除左侧浮动、修改div.fr的高度。

.content {
    clear: left;
}
.fr {
    height: 100px;
}

image.png

从图中可以看到,div.fl不在排列在content的左侧,但是由于元素顺序div.fl在content元素之前,所以content向下一行展示。但是由于没有清除右边浮动元素,div.fr仍旧可以排列在content元素的右侧。

再将content元素设置clear:both,即清除双侧浮动元素

image.png

可以看到div.fr不再排列在content元素的右侧,还是由于元素顺序的问题,content元素继续向下排列。
那么既然浮动元素不在正常流中,父元素中其实只有content元素,那么content元素的上方空间是如何填充的呢?

其实上述例子中clear在工作时,会为目标元素自动填充上外边距,使content排列在浮动元素的下方。众所周知,margin就是哪里不够补哪里,所以此处通过margin去进行空间填充,也并不意外。

那么了解完浮动以及常规清除浮动的方法(clear)后,我们再来理解一下,为何BFC能够清除浮动。

众所周知,父元素的高度没有进行具体设定时,默认为auto,就是刚刚好能包裹住子元素内容的高度。那么在以下场景,父元素中只有浮动元素,也就是说没有一个正常的子元素,父元素的高度就是0px。

<div id="div">
  <div class="fl">float div</div>
    <div class="fr">float right</div>
</div>

image.png

这种情况就是所谓的高度塌陷。 那么在没有正常流元素的情况下,无法通过设置clear属性去清除浮动,来撑高父元素。这种情况就非常适合使用BFC来清除浮动。

此处通过以下设置,来将父元素升级为一个BFC。

#div {
  overflow: hidden;
}

image.png

可以看到,即使只有浮动元素,父元素的高度也被撑开了。
再来回忆一遍BFC的概念,独立的布局单位,内部的子元素不会影响到外部。所以当父元素的高度塌陷后,溢出的浮动元素必然影响其他元素。所以作为一个BFC会对浮动元素进行包裹,避免溢出。那么外在的表现就是BFC父元素在计算高度时需要将浮动元素也计算进去。

三、总结

梳理完一遍BFC、浮动等CSS知识点,对于CSS的设计思路也有所感悟。对于越来越复杂的DOM结构及CSS属性,BFC是一种非常灵活、高效的处理方式,通过直接隔离出一块单独的渲染区域来避免一些样式互相影响造成的问题、避免牵一发动全身的样式灾难。另外又可以在这个隔离区内使用其他的布局规则,而不影响外部的布局方式,在处理一些特定场景会更加灵活。