BFC究竟是个什么东东?

4,019 阅读10分钟

FC

FC是Formatting Context的简写,直译过来是格式化上下文。其实并不是什么复杂的东西,个人对于它的理解是:FC就是html页面中的某个元素内的一套渲染规则,决定了其子元素如何布局,以及和其他元素在html页面中的位置

比如说BFC, BFC是block formatting context,也就是块级格式化上下文,是用于布局块级盒子的一块渲染区域,举个例子来说就是一个position为absolute(触发BFC的条件之一)的div就是一个BFC。

常见的FC还有

  • IFC(inline formatting context,即内联级元素内的渲染规则
  • GFC(grid formatting context,display为grid的元素内的渲染规则)
  • FFC(flex formatting context,display为flex的元素内的渲染规则)。

什么是触发XFC(x代表B,I,G,F其中之一)?

即通过设置某个元素的某些属性值来使得该元素应用某种渲染规则。例如将一个div的display属性设置为grid,那么就称触发了GFC,该元素内就形成了一个GFC,该元素内的渲染规则就要使用GFC规则

BFC

如何触发BFC?

即如何让某元素内形成BFC环境

某个元素满足下列条件之一就可形成一个BFC

  1. 该元素是根元素,即<html></html>标签内就是一个BFC环境
  2. float的值不为none
  3. overflow的值不为visible
  4. display的值为inline-block、table-cell、table-caption
  5. position的值为absolute或fixed

BFC的渲染机制(BFC特性)

1.在块格式化上下文中,从包含块的顶部开始,垂直地一个接一个地排列盒子。

In a block formatting context, boxes are laid out one after the other, vertically, beginning at the top of a containing block.

第一条特性我觉得描述得并不严谨,应该是块级盒子会在垂直方向上一个接一个放置

那什么是块级盒子呢?下面是w3c文档对块级盒子的描述

Block-level elements are those elements of the source document that are formatted visually as blocks (e.g., paragraphs). The following values of the 'display' property make an element block-level: 'block', 'list-item', and 'table'.

块级元素是那些在源文档中可视为块的元素(如,段落p标签)。display属性为如下属性的元素是块级元素:blocklist-item, table

Block-level boxes are boxes that participate in a block formatting context. Each block-level element generates a principal block-level box that contains descendant boxes and generated content and is also the box involved in any positioning scheme. Some block-level elements may generate additional boxes in addition to the principal box: 'list-item' elements. These additional boxes are placed with respect to the principal box.

其大致意思: 块级盒子就是那些参与块级格式化上下文的盒子(废话-_-)。每个块级元素都会产生一个主要的块级盒子来容纳子元素和生成的内容,并且是定位模式中会涉及到的元素(个人理解就是块级盒子会影响元素的位置)。一些块级元素也可能产生除主要盒子之外的附属盒子比如list-item(li)元素

Except for table boxes, which are described in a later chapter, and replaced elements, a block-level box is also a block container box. A block container box either contains only block-level boxes or establishes an inline formatting context and thus contains only inline-level boxes. Not all block container boxes are block-level boxes: non-replaced inline blocks and non-replaced table cells are block containers but not block-level boxes. Block-level boxes that are also block containers are called block boxes.

除了后面一章描述的table盒子和替换元素(浏览器根据元素的标签和属性,来决定元素的具体显示内容。如img和input标签)之外,块级盒子也是块级容器盒子(注意两者的区别)。一个块级容器盒子要么只容纳块级盒子,要么就会建立内联格式化上下文,因此也只容纳内联级盒子(我的理解:一个盒子要么是块级盒子,要么就是内联级盒子)。并不是所有的块级容器盒子都是块级盒子: 非替换内联块和单元格(td, th)不是块级盒子。既是块级盒子,同时又是块级容器才能被称为块盒子。(最后一句我也是醉了,在下水平有限,不能get到原文想要表达的意思)

The three terms "block-level box," "block container box," and "block box" are sometimes abbreviated as "block" where unambiguous.

“块级盒子”、“块容器盒子”和“块盒子”这三个术语有时在没有歧义的时候可以简写为“块”,(这句话一开始我理解错了,感谢@Tipwheal的指正!)

那什么是没有歧义的时候呢?个人理解是“块级盒子”、“块容器盒子”和“块盒子”都是指块级盒子的时候可以简写为块,即diplay为block的元素。(笔者也不太确定,如果有朋友理解,希望您能在评论中留言)

2.垂直方向上的距离由margin决定,属于同一个BFC的两个相邻Box的margin会发生重叠。

The vertical distance between two sibling boxes is determined by the 'margin' properties. Vertical margins between adjacent block-level boxes in a block formatting context collapse.

这个垂直方向上的距离由margin决定通俗易懂,不需要赘述。属于同一个BFC的两个相邻Box的margin会发生重叠

这个重叠主要会有两种情况: 1.上下相邻的两个元素 2.父元素与子元素的margin发生重叠

上下相邻的两个元素的margin会重叠

重叠的规则是:两者之间的垂直方向上的距离取决于谁的margin大

验证一下

.top, .btm {
  width: 200px;
  height: 200px;
}
.top {
  margin-bottom: 20px;
  background: deepskyblue;
}
.btm {
  margin-top: 30px;
  background: darkorange;
}
<div id="app">
    <div class="top"></div>
    <div class="btm"></div>
</div>

这个时候top与btm之间的距离不是50px,而是30px

左边的图中间的部分是top的margin-top,右边的图的中间部分是btm的margin-top,可以看到两者的距离就是btm的margin-top的距离

那么问题来了,怎么消除上下margin重叠呢?

给需要去除margin重叠的上下元素加float属性

.top, .btm {
  width: 200px;
  height: 200px;
  float:left;
  clear: both; /* 使元素能够垂直排列 */ 
}
.top {
  margin-bottom: 20px;
  background: deepskyblue;
}
.btm {
  margin-top: 30px;
  background: darkorange;
}

也可以用border,padding替代margin达到想要的效果。

我认为上下相邻的两个元素的margin重叠是没有必要消除的,除非想要实现某些效果时才需要消除

父元素与子元素的margin发生重叠

如果父元素的margin-top为0,padding-top为0,没有border,而子元素的margin-top不为0,那么父元素距离上方的距离就会是子元素margin-top的值,margin-bottom同理

举个栗子

* {
  padding: 0;
  margin: 0;
}
#app {
  background: yellowgreen;
}

.top {
  background: deepskyblue;
  height: 200px;
  margin: 20px;
}
.footer {
  height: 200px;
  width: 200px;
  background: yellow;
}
<div id="app">
    <div class="top"></div>
</div>
<div class="footer"></div>

可以看到父元素app由于top与上下都产生了外边距

消除父子margin重叠的方法:

  1. 给父元素加border
  2. 设置父元素的padding或者margin
  3. 给父元素添加overflow:hidden

3.bfc的区域不会与float的元素区域重叠。

<div class="left"></div>
<div class="main"></div>
.main {
  background: deepskyblue;
  width: 400px;
  height: 400px;
}
.left {
  float: left;
  width: 200px;
  height: 200px;
  background: darkorange;
}

浏览器渲染的结果如下所示

main与left产生了重叠

而当设置overflow:hidden

.main {
  background: deepskyblue;
  width: 400px;
  height: 400px;
  overflow: hidden;
}

就消除了float的影响

利用float可以形成文字环绕效果,但是也可以利用这条特性消除文字环绕效果

4.在块格式化上下文中,每个box的左外边缘都与包含块的左边缘相接触(对于从右到左的格式化,右边缘相接触)

在块格式化上下文中,每个box的左外边缘都与包含块(注意这个名词)的左边缘相接触(对于从右到左的格式化,右边缘相接触)。即使在浮动存在的情况下也是如此(尽管box的box's line boxes 可能会因为浮动而收缩),除非box建立了一个新的块格式上下文(在这种情况下,box本身可能会因为浮动而变得更窄)。

原文是这样的

In a block formatting context, each box's left outer edge touches the left edge of the containing block (for right-to-left formatting, right edges touch). This is true even in the presence of floats (although a box's line boxes may shrink due to the floats), unless the box establishes a new block formatting context (in which case the box itself may become narrower due to the floats).

这段话在网上解释的中文版本非常多,于是就产生了歧义

真正要理解这句话首先要搞懂一个概念,什么是包含块?

In CSS 2.1, many box positions and sizes are calculated with respect to the edges of a rectangular box called a containing block. In general, generated boxes act as containing blocks for descendant boxes; we say that a box "establishes" the containing block for its descendants. The phrase "a box's containing block" means "the containing block in which the box lives," not the one it generates.

在CSS 2.1中,许多盒子的位置和大小是根据一个矩形框(称为包含块)的边缘来计算的。通常,生成的盒子充当包含子盒子的块;我们说一个盒子为它的后代“建立”了包含它的块。短语“a box's contains block”的意思是“盒子所在的包含块”,而不是它生成的块。

Each box is given a position with respect to its containing block, but it is not confined by this containing block; it may overflow.

每个盒子都被赋予相对于其包含块的位置,但不受该包含块的限制;它可能溢出。

那么是不是所有元素的包含块就是其父元素的content-box呢?

答案当然是:不是!!

只能说,大多数情况下,包含块就是这个元素最近的祖先块元素的内容区。某个元素的包含块与其直接父元素有可能一点关系都没有。举一个例子:一个position为absolute的元素,包含块就是由它的最近的 position 的值不是 static (fixed, absolute, relative, or sticky)的祖先元素的内边距区的边缘组成的。如果要深入研究请参考这篇文章我所了解的包含块

在懂了包含块的概念之后我们再来看这条特性,就豁然开朗。

那么即使在浮动存在的情况下也是如此,这个该怎么理解呢?我们知道当给元素设置float之后,只要宽度足够,元素就会一个接一个横向排列,那从第二个元素起,岂不是不会接触包含块的边缘了?是不是文档错了呢?

文档的下文还写着除非box建立了一个新的块格式上下文,而给元素添加float属性就会形成新的BFC,所以文档的描述是很精确的

5.计算bfc的高度时,浮动元素也参与计算

这条特性就是就是解决子元素浮动导致父元素无法撑开的关键

.main {
  background: deepskyblue;
}
.main div {
  height: 100px;
  width: 100px;
  float: left;
}
<div class="main">
    <div></div>
    <div></div>
</div>

上面的渲染结果为页面一片空白...

而让class为main的父级div形成BFC后

.main {
  background: deepskyblue;
  position: absolute;  /* 触发BFC */
}

渲染结果如下:

父元素就被撑开了。并且position设置为absolute的块级元素的width是不会占满剩余空间,如果不设置宽度并且没有子元素,其宽度为0,如果有子元素,其宽度由子元素撑开

6.bfc就是页面上的一个独立容器,容器里面的子元素不会影响外面元素。

这个特性通俗易懂,不需要解释

总结

我们可以用BFC机制完成的事情:

  1. 清除浮动的影响
  2. 清除margin重叠

ps:本人前端小菜鸟一只,如果文中有错,欢迎各位看客能够指正