CSS中BFC初识

94 阅读16分钟

Understanding Block Formatting Contexts in CSS

文章来源于: www.sitepoint.com/understandi…

Block Formatting Contesxts = BFC

Key Takeaways(关键点速览)

  • BFC 是网页渲染CSS的的一部分, 它包含如何设置(布局)我们的块框(也就是block box) 它可以通过添加一些特定的css属性来创建. 例如 overflow: scroll, display:flex, float: left
  • BFC会导致外边距塌陷(collapse), 这意味着2个兄弟级别(sibling boxes)的盒子的垂直距离不是他们各自页边距的总和.(取最大值) 不过, 我们可以创建一个新的BFC可以防止这个边距塌陷的问题
  • BFC可以用于包含浮动元素(float), 在容器中包含一个浮动元素的时候, 定义一个BFC可以帮助我们包裹这些元素同时维护页面的正常流
  • BFC可以防止文本围绕着浮动元素进行换行. 通过对 p 元素建立一个新的BFC, 它可以不在以容器的左边缘开始, 从而防止文本围绕浮动元素进行换行
  • BFC在多列布局中也很有效果. 通过在多列布局中的一个列中建立一个新的BFC, 他将要占用前一列填充后剩下的所有的空间, 从而防止在一些浏览器中最后一列切换到新的一行中

块级上下文(Block Formatting Context)是web页面的CSS渲染的一部分, 它包含如何设置(布局)我们的块框. 它所属的定位方案为正流式(normal flow). 根据W3C:

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.

浮动元素、绝对定位元素、非块框的块容器(如内联块 ...), 以及overflow属性值不是 visible的块框(除非该值已传播到视口)会为其内容建立新的块格式化上下文

(这里的块框可能不太好理解, 可以当成就是我们的 display: block 的元素. 当然这个定义可能不严谨, 注意inline-block 不属于 块框

这部分很好的总结了我们的BFC是如何形成的, 我们尝试用一种更容易理解的方式重新定义它. BFC是一个HTML 盒子. 他满足如下的条件之一:

  • float的值不是none
  • position的值既不 是static 也不 relative
  • display的值为table-cell, table-caption, inline-block, flex, or inline-flex
  • overflow 的值不是 visible

(overflow的默认值是 visible )

Creating A BFC(创建一个BFC)

我们可以显式的触发BFC(从而避免我们开始说的一些问题) . 如果我们想要创建一个新的BFC, 只需要添加上面说的那些css条件.

例如

<div class="container">
  这里有一些内容
</div>

一个新的BFC可以通过添加任意一个所需要的CSS属性进行创建, 就像overflow: scroll, overflow: hidden, display: flex, float: left, or display: table 这些属性, 虽然这些元素都可以创建一个BFC, 但会带来一些其他的效果, 例如:

  • display: table 可能会导致响应性问题
  • overflow: scroll 可能显示不需要的滚动条
  • float: left 将把元素推到左边,其他元素将环绕它
  • overflow: hidden 将裁剪溢出的元素
  • (补充一点 <html> 根元素也产生BFC)

所以, 我们创建一个新的BFC的时候, 我们需要根据当前需求选择最好的方式. 为了统一期间, 在这片文章的所有例子中我们都使用overflow: hidden

      .container {
        overflow: hidder;
      }

除了 overflow: hidden 之外, 我们也可以使用上面任何一个属性

Alignment Of Boxes In A BFC(在BFC中的元素对齐)

在BFC中对齐盒子(boxes)

在W3C规范中提出:

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 boxes) 可能因浮动元素而缩小), 除非该框建立了新的块格式化上下文(在这种情况下, 由于浮动元素, 框本身可能变窄).

(这里也解释了为何我们的一个块级元素中, 如果有元素出现了浮动, 文本会包裹浮动元素进行换行.)

image.png

图源: www.sitepoint.com/understandi…

简单的说. 就像我们上面的图中所示, 在BFC的内的所有盒子都要左对齐, 并且它们的左外缘接触包含块的左边缘. 在最后一个盒子中, 我们可以看见有一个棕色的浮动盒子的情况下, 还会有一个绿色的元素接触到左边缘. 至于为啥, 我们在下面进行讨论

这里给出一个示例代码

image.png

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>algnment-of-boxes-in-bfc</title>
    <style>
      .container {
        width: 300px;
        height: 300px;
        overflow: hidden;
        background-color: cornsilk;
        border: solid 1px black;
      }
      .float-left {
        float: left;
        width: 50px;
        height: 50px;
        background-color: cornflowerblue;
      }
    </style>
  </head>
  <body>
    <div class="container">
      <div class="float-left"></div>
      <p>
        Lorem ipsum dolor sit amet consectetur adipisicing elit. Placeat porro
        nobis ullam saepe cum earum eligendi eveniet debitis a dolore rem
        possimus, quo unde. Provident quidem nam consectetur assumenda et.
      </p>
    </div>
  </body>
</html>

A BFC Causes Collapsing of Margin(BFC导致margin折叠)

BFC会导致边距折叠

我们将看一下 CSS2.2中关于BFC最后一段描述了

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.

在一个BFC中, 盒子是在从包含块的顶部开始, 垂直方向上, 一个接着一个布局的. 这个垂直方向的间隙是依赖于兄弟级别的盒子的margin属性决定的. 在BFC下, 我们的邻接(adjacent)块级盒子的垂直方向的margin会存在折叠(collapse)

为了解释这段概念中的一些名词, 我们需要额外的补充一些说明, 可以参考补充部分

注意这里的 邻接 并不是只有兄弟元素, 还有父子嵌套的那种

具体表现形式为:

<!DOCTYPE html>
<html lang="en">
  <head>
    <title>margin test</title>
    <style>
      .box1 {
        width: 50vw;
        height: 30vh;
        background-color: cornflowerblue;
        overflow: hidden;
      }

      .box2 {
        width: 10vw;
        height: 10vh;
        background-color: cornsilk;
        margin-top: 100px;
      }
    </style>
  </head>
  <body>
    <div class="box1">
      <div class="box2"></div>
    </div>
  </body>
</html>

给box1添加了overflow: hidden 会使得我们的box2处于box1创建的BFC中, 而box1是HTML创建的BFC中, 2者不处于同一个BFC, 从而不会让margin出现塌陷.

当然, 给元素加上border也会避免margin塌陷.

我们在多看一些BFC导致的margin塌陷的问题吧.

在正常流中, 盒子从容器块的顶部开始, 一个接一个垂直放置. 两个兄弟盒子之间的垂直距离由两个兄弟盒子的单个边距决定, 而不是两个边距之和. (上面的定义)

image.png

图源:www.sitepoint.com/understandi…

在上图中, 我们认为已经创建了一个块格式化上下文, 其中红色框( div )包含两个绿色兄弟元素((p 元素)

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>margin塌陷问题</title>
    <style>
      .container {
        background-color: red;
        overflow: hidden; /* 在Container元素内部创建一个BFC */
      }
      p {
        background-color: lightgreen;
        margin: 10px 0;
      }
    </style>
  </head>
  <body>
    <div class="container">
      <p>Sibling 1</p>
      <p>Sibling 2</p>
    </div>
  </body>
</html>

理想情况下, 两个兄弟元素之间的margin应该是两个元素margin之和(20px), 但实际上是10px. 这就是所谓的边距折叠(collapse margin). 在兄弟元素的margin不同的情况下,较高的margin将占上风

如何解决?

只需要让其不满足我们margin塌陷的几个要求, 也就是前面提到的W3C中的marign collapse 情况

Using BFC to Prevent Margin Collapse(使用BFC阻止margin折叠)

这听起来可能有点令人困惑, 因为我们在上面讨论了BFC会导致外边距折叠. 但是有一点我们必须记住, 只有在相同的BFC中, 邻接块(不仅仅兄弟关系, 父子也可能)之间的垂直外边距才会折叠.

如果它们属于不同的BFC, 那么它们之间的margin不会折叠(collapse).

因此, 通过创建一个新的BFC, 我们可以防止外边距折叠(margin collapse)

这样这几个元素就不属于一个BFC中了, 自然不会出现一个BFC中的margin collapse问题

<!DOCTYPE html>
<html lang="en">
  <head>
    <title>handle margin collapse</title>
    <style>
      .container {
        background-color: red;
        overflow: hidden; /*在container内部创建了一个BFC*/
      }
      p {
        background-color: lightgreen;
        margin: 10px 0;
      }
​
      /* 解决方式, 创建一个新的bfc */
      .newBFC {
        overflow: hidden; /*在container内部创建了一个BFC*/
      }
    </style>
  </head>
  <body>
    <div class="container">
      <p>Sibling 1</p>
      <p>Sibling 2</p>
      <div class="newBFC">
        <p>Sibling 3</p>
      </div>
    </div>
  </body>
</html>

image.png

Using BFC to Conatin Float(使用BFC来包裹浮动元素)

使用BFC来包裹浮动元素, 解决浮动元素带来的宽高塌陷问题

在BFC中可以包含浮动元素. 我们经常会遇到容器中出现了浮动元素, 使得容器没有高度, 因为浮动元素会脱离页面的正常流

在社区中有一个流行的方案解决这个问题, 也就是使用"cleared"伪元素. 不过使用BFC也可以解决这个问题

之前在w3c中看见的, 在BFC中, 盒子从包含块的顶部开始, 一个接一个地垂直布局(浮动元素也会这样)

<!DOCTYPE html>
<html lang="en">
  <head>
    <title>handle margin collapse</title>
    <style>
      .container {
        background-color: cornflowerblue;
        overflow: hidden; /*在container内部创建了一个BFC*/
      }
      .container div {
        background-color: cornsilk;
        width: 10vw;
        height: 10vh;
        margin: 0 10px;
        float: left;
      }
    </style>
  </head>
  <body>
    <div class="container">
      <div></div>
      <div></div>
    </div>
  </body>
</html>

image.png

使用cleanfix处理

<!DOCTYPE html>
<html lang="en">
  <head>
    <title>handle margin collapse</title>
    <style>
      ...
      .cleanfix::after,
      .cleanfix::before {
        content: "";
        display: block;
        clear: both;
      }
    </style>
  </head>
  <body>
    <div class="container cleanfix">
      <div></div>
      <div></div>
    </div>
  </body>
</html>

w3c中规定了我们的clear: 应用于非浮动的块级框时:

both: 要求框的顶部边框边缘位于源文档中较早生成的任何左浮动和右浮动框的底部外边缘下方, 所以也就会让我们的父容器有了浮动的宽高了

Using BFC To Prevent Text Wrapping(使用BFC阻止文本换行)

这个是解决我们在文章一开始提出的问题

有时, 浮动div周围的文本包围了它(如下图中的图1所示), 但在某些情况下, 这不是我们所希望的, 我们希望出现如图2所示的外观. 为了解决这个问题, 我们可以使用外边距, 但我们也可以使用块格式化上下文来解决这个问题

image.png

图源: www.sitepoint.com/understandi…

首先理解为什么文本会自动换行. 为此, 我们必须理解当元素浮动时盒子模型是如何工作的. 这是我之前讨论BFC留下的部分. 让我们理解下图中的图1中发生了什么

image.png

<!DOCTYPE html>
<html lang="en">
  <head>
    <title>float element in bfc</title>
    <style>
      .container {
        width: 300px;
      }
      .floated {
        float: left;
        background-color: rgba(100, 148, 237, 0.8);
        width: 100px;
        height: 100px;
      }
      p {
        background-color: rgb(00, 00, 00);
        color: #fff;
      }
    </style>
  </head>
  <body>
    <div class="container">
      <div class="floated">Floated div</div>
      <p>
        岁月
        你别催,走远的我不追,我不过是想弄清原委,谁能告诉我这是什么呢,她的爱在心里埋葬了,抹平了几年了仍有余威,是不能原谅,却无法阻挡,爱意在夜里翻墙,是空空荡荡,却嗡嗡作响,谁在你心里放冷枪,旧爱的誓言,像极了一个巴掌,每当你记起一句,就挨一个耳光,然后好几年都闻不得,闻不得女人香
        --给自己的歌
      </p>
    </div>
  </body>
</html>

上图中的整个黑色区域表示 p 元素. 正如我们所看到的, p 元素不会移动, 但它会位于浮动元素的下方. p 元素(指文本行)的行盒发生移位. 因此,行盒水平缩小, 为浮动元素腾出空间.

随着文本的增加, 它最终将包裹在浮动元素之下, 因为行盒不再需要移动, 因此出现了上图所示的包裹效果. 这解释了即使存在浮动元素, 段落也会接触到包含框的左边缘, 以及行框如何缩小以适应浮动元素

如果我们能够移动整个 p 元素, 那么这个换行问题将得到解决

在讨论解决方案之前, 让我们再回顾一下W3C规范的内容

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

在BFC中, 每个盒子的左外边缘触碰包含盒子的左边缘(对于从右到左的格式化, 右边缘触碰). 即使存在浮动元素也是如此(尽管盒子的行盒(inline boxes) 可能因浮动元素而缩小), 除非该盒子建立了新的BFC(在这种情况下, 由于浮动元素, 盒子本身可能变窄).

据此, 如果 p 元素建立了一个新的块格式化上下文, 那么它将不再接触容器块的左边缘. 这可以通过简单地将 overflow: hiddenp 元素相加来实现. 通过这种方式创建一个新的块格式化上下文解决了文本包围浮动对象的问题

image.png

Using BFC in Multi-column Layouts(在多列布局中使用BFC)

如果我们创建一个横跨整个容器宽度的多列布局, 在某些浏览器中, 最后一列有时会拖到下一行, 这可能是因为浏览器将列的宽度四舍五入, 总宽度超过了容器的宽度. 然而, 如果我们在布局的一列中建立一个新的BFC, 它将始终占用前一列填充后剩余的空间, 实现更好的流效果

让我们使用一个包含3列的多列布局的例子

<div class="container">
  <div class="column">column 1</div>
  <div class="column">column 2</div>
  <div class="column">column 3</div>
</div>
.column {
  width: 31.33%;
  background-color: green;
  float: left;
  margin: 0 1%;
}
/*
  在最后一个列上建立一个BFC
*/
.column:last-child {
  float: none;
  overflow: hidden;
}

image.png

现在, 即使容器的宽度略有变化, 布局也不会中断. 当然, 对于多列布局来说, 这并不一定是一个好的选择, 但它可以防止最后一列被删除的问题. 在这种情况下, Flexbox可能是一个更好的解决方案, 但这应该有助于说明元素在这种情况下的行为

补充

adjacent element 邻接元素

这里说的邻接(adjacent)元素, 我们可能会直接当成兄弟级别的元素. 但在CSS的margin 的 邻接(adjoining)说法中, 对它有着更加清晰的定义

Two margins are adjoining if and only if:

  • both belong to in-flow block-level boxes that participate in the same block formatting context

  • no line boxes, no clearance, no padding and no border separate them

  • both belong to vertically-adjacent box edges, i.e. form one of the following pairs:

    • top margin of a box and top margin of its first in-flow child
    • bottom margin of box and top margin of its next in-flow following sibling
    • bottom margin of a last in-flow child and bottom margin of its parent if the parent has 'auto' computed height
    • top and bottom margins of a box that does not establish a new block formatting context and that has zero computed 'min-height', zero or 'auto' computed 'height', and no in-flow children

A collapsed margin is considered adjoining to another margin if any of its component margins is adjoining to that margin.

只有如下的情况, 2个外边距才算是邻接(adjoining )的

  • 2者都属于参与同一个BFC的流内块级盒子

  • 不在行盒, 没有清除(cleanfix), 没有padding和边框分隔

  • 2者都是垂直方向的邻接(adjacent)盒子的边缘, 也就是下面这些的一种

    • 盒子的顶部外边距与其第一个流内子元素的顶部外边距
    • 盒子的底部外边距与其下一个流内后续兄弟元素的顶部外边距
    • 最后一个流内子元素的底部外边距与其父元素的底部外边距(如果父元素的 height 计算值为 auto
    • 盒子本身的上下外边距(该盒子不建立新的块级格式化上下文且其计算的 'min-height'为零,计算的 'height' 为零或 'auto',且没有流内子元素)

相邻的外边距可以由不是兄弟元素生成, 所以我们上面BFC中提到的邻接(adjacent)元素会有外边距塌陷, 在父子关系的元素下也会生成.

  • 当两个或多个外边距折叠时, 结果的外边距宽度是折叠外边距宽度中的最大值
  • 如果盒子的上下外边距相邻, 则可能会通过它折叠

所以我们说的margin折叠, 是一个在规范中定义了具体的形式. 并不啥特殊情形

Container Block (包含块)

元素盒子的定位和大小有时是相对于某个矩形计算的, 该矩形称为该元素的包含块

  • 根元素(HTML)所处的包含块是一个矩形, 称为初始包含块
  • 对于其他元素, 如果元素的定位是relative或static, 则包含块由最近的祖先盒子的内容边缘形成, 该祖先盒子是块容器或建立了BFC
  • 如果元素的position属性为absolute, 则包含块由最近的具有 absolute, relative或fixed定位属性的祖先元素建立(这也是定位的依据吧), 如果没有这样的祖先, 包含块就是初始包含块

对于元素的position属性, 它的默认值是 static

htmlspecs.com/css/css22/v…

而static对于元素: 是一个正常的盒子, 按照正常流布局

例如

<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN">
<html>
  <head>
    <title>包含块示例</title>
  </head>
  <body id="body">
    <div id="div1">
      <p id="p1">这是第一段中的文本...</p>
      <p id="p2">
        这是<em id="em1">第二段<strong id="strong1">中的</strong>文本。</em>
      </p>
    </div>
  </body>
</html>

包含块的建立如下:

生成的框包含块由以下元素建立
html初始包含块 (UA(用户代理浏览器)-依赖)
bodyhtml
div1body
p1div1(position为static)
p2div1(position为static)
em1p2(position为static)
strong1p2(position为static)

如果我们为div1设置定位

#div1 { position: absolute; left: 50px; top: 50px }

它的包含块不再是“body”;而是初始包含块(因为没有其他已定位的祖先框)

Conclusion(结论)

对这位前辈的博客, 借助翻译工具进行了翻译, 同时在翻译的过程中参阅了w3c的内容, 从而对BFC加深理解, 对于一些margin的一些特殊情况, 浮动元素的影响有着更深刻的理解.

BFC的意义是: 它控制着网页上元素的布局. 它在元素的定位和样式上起着至关重要的作用, 尤其是在复杂的布局中. BFC有助于隔离文档中浮动的部分, 这可以防止元素意外重叠

如果朋友们想深入了解, 请务必查看W3C中相关的解释

我们所不理解的, 只是在于不了解其在前辈们的规范中的如何定义. 所以一切皆有定义. 就像侯捷先生说的

"源码之前, 了无秘密"

一起加油 :D

参考

  1. 原文来源于: www.sitepoint.com/understandi…
  2. css视觉模型: www.w3.org/TR/CSS22/vi…
  3. css的翻译版本: htmlspecs.com/css/css22/v…