对BFC的理解

在前端的面试中,相对JavaScript而言,CSS布局方面考察的内容会相对少一些,其中BFC是布局样式方面常考的一个考点。

什么是BFC

BFC,全称为Block Formatting Context,翻译过来即块格式化上下文。

之前在其他文章中看到的说明是,网页上一个独立且隔离的渲染区域。现在呢,我稍微查阅了一些官方的信息。

在了解BFC之前,我们需要先了解一些其他概念:

包含块(containing block)

containing block

A rectangle that forms the basis of sizing and positioning for the boxes associated with it. Notably, a containing block is not a box (it is a rectangle), however it is often derived from the dimensions of a box. Each box is given a position with respect to its containing block, but it is not confined by this containing block; it can overflow. The phrase “a box’s containing block” means “the containing block in which the box lives,” not the one it generates.

In general, the edges of a box act as the containing block for descendant boxes; we say that a box “establishes” the containing block for its descendants. If properties of a containing block are referenced, they reference the values on the box that generated the containing block. (For the initial containing block, values are taken from the root element unless otherwise specified.)

简单翻译一下:

包含块

一个矩形,是与之相关的盒子的尺寸和定位的基础。值得注意的是,包含块并不是一个盒子(它是一个矩形),但它通常是从盒子的尺寸中衍生出来的。每个盒子都有一个相对于其包含块的位置,但它不受包含块的限制,可以溢出。“盒子的包含块"指的是 "盒子所在的包含块",而不是盒子产生的包含块。
一般来说,一个盒子的边缘作为后代盒子的包含块;我们说一个盒子为它的后代 "建立"了包含块。如果引用了包含块的属性,则引用的是生成包含块的盒子的值。(对于初始包含块,除非另有说明,否则其值取自根元素)。

从上述定义中,我们可以简单认为一个盒子其产生的包含块就是除了盒子的margin、border和padding以外最内部的那块区域。

边缘(edge)

edge

The perimeter of each of the four areas (content, padding, border, and margin) is called an edge

边缘

四个区域(内容、填充、边框和外边距)中每个区域的周长称为边缘

边缘edge,定义了盒模型中的四个边缘。

initial containing block

The containing block of the root element. The initial containing block establishes a block formatting context.

初始包含块

指根元素的包含块。初始包含块建立了块格式上下文。

格式化上下文(FC,formatting context)

formatting context

A formatting context is the environment into which a set of related boxes are laid out. Different formatting contexts lay out their boxes according to different rules. For example, a flex formatting context lays out boxes according to the flex layout rules [CSS-FLEXBOX-1], whereas a block formatting context lays out boxes according to the block-and-inline layout rules [CSS2]. Additionally, some types of formatting contexts interleave and co-exist: for example, an inline formatting context exists within and interacts with the block formatting context of the element that establishes it, and a ruby container overlays a ruby formatting context over the inline formatting context in which its ruby base container participates.

A box either establishes a new independent formatting context or continues the formatting context of its containing block. In some cases, it might additionally establish a new (non-independent) co-existing formatting context. Unless otherwise specified, however, establishing a new formatting context creates an independent formatting context. The type of formatting context established by the box is determined by its inner display type. E.g. a grid container establishes a new grid formatting context, a ruby container establishes a new ruby formatting context, and a block container can establish a new block formatting context and/or a new inline formatting context. See the display property.

格式化上下文
格式化上下文是一组相关盒子布局所在的环境。不同的格式上下文根据不同的规则布局盒子。例如,弹性格式上下文根据弹性布局规则[CSS-FLEXBOX-1]布局方框,而块格式上下文则根据块和行内布局规则[CSS2]布局盒子。此外,某些类型的格式化上下文会交错并存:例如,行内格式化上下文存在于建立行内格式化上下文的元素的块格式化上下文中,并与之交互;ruby容器会在其ruby基本容器所参与的行内格式化上下文上叠加ruby格式化上下文。
盒子要么建立新的独立格式上下文,要么延续其包含块的格式上下文。在某些情况下,盒子可能会额外建立一个新的(非独立的)共存格式上下文。除非另有规定,否则建立新的格式上下文会创建一个独立的格式上下文。盒子建立的格式上下文类型由其内部显示类型决定。例如,网格容器会建立新的网格格式上下文,ruby容器会建立新的ruby格式上下文,而块容器可以建立新的块格式上下文和/或新的行内格式上下文。请参阅显示属性。

由上述定义可知每种FC都有其不同于其他FC的布局规则,由此可知每种FC的创建是声明了一个应用其布局规则的区域。

而盒子的FC类型由其内部显示类型决定,即display属性的第二个值。

BFC

先看W3C中的定义:Block formatting contexts

Boxes in the normal flow belong to a formatting context, which may be block or inline, but not both simultaneously. Block-level boxes participate in a block formatting context. Inline-level boxes participate in an inline formatting context.

Floats, absolutely positioned elements, block containers (such as inline-blocks, table-cells, and table-captions) that are not block boxes, and block boxes with 'overflow' other than 'visible' (except when that value has been propagated to the viewport) establish new block formatting contexts for their contents.

In a block formatting context, boxes are laid out one after the other, vertically, beginning at the top of a containing block. 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.

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).

翻译过来大致是下面的意思:

正常流中的盒子属于格式化上下文,可以是块或行内,但不能同时是块和行内。块级盒子属于块格式上下文,行内级盒子属于行内格式上下文。

浮动元素、绝对定位元素、非块盒子的块容器(如inline-block、table-cells,以及table-captions),以及 'overflow' 不是 'visible' 的块盒子(除非该值已传播到viewport),都会为其内容建立新的块格式上下文。

在块格式化上下文中,盒子从包含块的顶部开始一个接一个地垂直排列。两个同胞盒子之间的垂直距离由 'margin' 属性决定。在块格式上下文中相邻块级盒子之间的垂直margin会折叠。

在块格式化上下文中,每个盒子的左外缘与包含块的左缘相接(对于自右向左的格式,则是右缘相接)。即使在浮动的情况下(尽管盒子的行盒可能因浮动而缩小)也是如此,除非盒子建立了新的块格式化上下文(在这种情况下,盒子自身可能会因浮动而变窄)。

其实看完这个定义我刚开始还有一点疑惑,细想了一下最后一段的意思可以对应以下情况:

前一个是浮动元素,后面紧跟的元素也会与盒子的左缘(或右缘)相接(也就是这个元素与前面的浮动元素会发生重叠),为了防止这个事情发生,可以为后面这个元素创建新的BFC,这样这个新的BFC就不会与外部BFC的左缘(或右缘)相接了,同时这个元素会因为前一个浮动元素的存在,而宽度变窄。

MDN中给出的定义:Block formatting context

这里给出的定义就是比较简短的一句话,翻译过来是:

块格式化上下文(BFC)是网页的可视化CSS渲染的一部分。它是块盒子布局的区域,也是浮动元素与其他元素交互的区域。

简而言之,就是块盒子内部的布局区域,既然是内部,也就与外部相隔离开,并且在这个区域内,浮动元素会与其他元素产生交互。

触发条件

由以上定义可知,容器满足以下条件之一,其产生的包含块便可以形成BFC:

  • 为文档的根元素(<html>
  • 正常流中的块级盒子
  • 浮动元素
  • 绝对定位元素
  • 非块盒子的块容器(如inline-block、table-cells,以及table-captions)
  • overflow不是visible的块盒子
  • displayflow-root的元素(the display property)

除此之外,MDN还有其他补充(应该都属于对非块盒子的块容器的补充):

  • 表格单元格(display属性为table-cell的元素,这是HTML表格单元格的默认设置)
  • 表格标题(display属性为table-caption的元素,这是HTML表格标题的默认设置)
  • display为table、table-row、table-row-group、table-header-group、table-footer-group(分别是HTML表格、表格行、表格主体、表格头部和表格底部的默认设置)或inline-table的元素隐式创建的匿名表格单元格
  • overflow属性不是visible或clip的块元素
  • contain属性为layout、content或paint的元素
  • 弹性项(display属性为flex或inline-flex的元素的直接子元素),如果它们本身并非flex或grid或table容器
  • 网格项(display属性为grid或inline-grid的元素的直接子元素),如果它们本身并非flex或grid或table容器
  • 多列容器(column-countcolumn-width属性不为auto的元素,包括设置column-count:1的元素)
  • column-span:all元素应始终创建新的格式化上下文,即使其不包含在多列容器中

布局规则

同时根据以上的定义,还可以得出BFC内部的内容遵循以下布局规则:

  • BFC的内容包含在包含块内
  • 内部的盒子从包含块的顶部开始一个接一个地垂直排列
  • 两个同胞盒子之间的垂直距离由'margin'属性决定
  • 相邻块级盒子之间的垂直'margin'会折叠
  • 每个盒子的左外缘与包含块的左缘相接(对于自右向左的格式,则是右缘相接)。即使在浮动情况下也是如此(尽管盒子的行盒子可能因浮动而缩小),除非该盒子建立了新的块格式化上下文(在这种情况下,盒子自身可能会因浮动而变窄)

同时BFC自身不会与同级的浮动元素发生重叠

应用场景

至此,就大致了解了BFC是什么,其触发形成机制,以及其内部的布局规则。

但通常情况下我们不会仅仅为了更改布局去创建新的BFC,而是为了解决特定的问题来创建BFC,比如定位和清除浮动,因为建立了新BFC的容器将可以:

  • 包含内部浮动元素(也就是浮动元素不会溢出容器之外)
  • 排除外部浮动元素(利用BFC不会与同级浮动区域重叠的规则)
  • 抑制margin折叠(通过在BFC的内部创建新的BFC使得其中相邻盒子的margin不发生折叠)

以下就是MDN中的几个例子:

包含内部浮动元素

<section>
  <div class="box">
    <div class="float">I am a floated box!</div>
    <p>I am content inside the container.</p>
  </div>
</section>
section {
  height: 150px;
}
.box {
  background-color: rgb(224, 206, 247);
  border: 5px solid rebeccapurple;
}
.float {
  float: left;
  width: 200px;
  height: 100px;
  background-color: rgba(255, 255, 255, 0.5);
  border: 1px solid black;
  padding: 10px;
}

这其实也是一种清除浮动、解决高度坍塌的方案,在过去有些前端开发人员会给.box这个div设置overflow: auto;或者hidden来使其内部形成BFC,这是一种方式,但这种解决方式存在一个问题,就是对于后续的开发者来说,他们可能不清楚为什么要使用overflow,如果要这么用,最好用注释说明清楚。

现在有一个新的displayflow-root可以让我们用来创建新的BFC,用它会更好,因为从字面上更好理解,创建的容器为其内部的流式布局创建新的上下文的行为类似于root元素(也就是浏览器中的<html>元素)。

排除外部浮动元素

这个例子中我们要实现双列布局,当然在现在CSS中,用弹性盒子实现更方便,但我们也可以通过BFC来实现。

在上一个例子中,可以看到浮动元素与后面的p元素重叠了,如果我们不想他们重叠,希望他们可以形成双列布局,就可以通过建立新的BFC来实现,比如给p元素套上一个div同时给其设置display: flow-root;来手动触发BFC的形成,因为BFC不会与任何同级浮动元素的margin盒子发生重叠。

<section>
  <div class="float">Try to resize this outer float</div>
  <div class="box"><p>Normal</p></div>
</section>
section {
  height: 150px;
}
.box {
  background-color: rgb(224, 206, 247);
  border: 5px solid rebeccapurple;
}
.float {
  float: left;
  overflow: hidden; /* required by resize:both */
  resize: both;
  margin-right: 25px;
  width: 200px;
  height: 100px;
  background-color: rgba(255, 255, 255, 0.75);
  border: 1px solid black;
  padding: 10px;
}

从而达到防止浮动元素与其他元素重叠。

阻止边距折叠

因为BFC内部的布局规则是相邻盒子间的垂直margin会折叠,如果我们不想起发生折叠,也可以利用创建BFC的方式来达到目的。

<div class="blue"></div>
<div class="red"></div>
.blue,
.red {
  height: 50px;
  margin: 10px 0;
}

.blue {
  background: blue;
}

.red {
  background: red;
}

如果不想使红色和蓝色两个元素的margin发生折叠,就可以给红色元素套上一个div并使其创建BFC,这个BFC就是一个独立渲染的区域,这样两个元素的垂直margin就不会发生折叠了。

总结

当我们使用Chrome的开发者工具,可以查看到网页内的块状元素其对应的盒模型,margin、border、padding,以及最里层的content,独立的布局渲染区域指的应该就是content这部分。

创建BFC的目的,就是想把容器内部的内容限定在这部分区域中,而不与外部元素产生作用,比如上个例子中阻止垂直margin折叠,就是把红色元素限定在一个新的BFC中,从而不与外部的蓝色元素发生作用,这样就不会出现margin折叠了。

参考资料

包含块(containing block)

边缘(edge)

W3C:Block formatting contexts

MDN:Block formatting context

热门相关:神秘总裁小小妻   管家会给你你想要的一切   不科学御兽   你老公都不知道你那轻浮的裤裆   重生之嫡女祸妃