likes
comments
collection
share

【4.5k字】最全面的BFC梳理,再也不怕面试官深究了!(含Demo)

作者站长头像
站长
· 阅读数 5

前言

本文首发于我的博客,欢迎点击增加浏览量。

前些天被人问到了 BFC ,回答的时候没答好。查阅资料,发现也没有能够从源头上系统化梳理BFC的文章,遂自己查阅文档写出本文。

在写这篇文章的过程中,我才发现原来外边距塌陷和 BFC 并不可以混淆而谈,外边距塌陷有它自己非常明显的充分必要条件,它的发生与 “相邻”的盒子 有关,而 BFC 只是解决它的一个办法而已,并不是只要生成 BFC 就能解决。

这篇文章还是查阅了非常多资料的,上面的Demo也都可以在我的git地址中找到,如果恰好对你们有所帮助的话请不要吝啬你们的点赞和收藏哦~谢谢。

什么是BFC?

BFC 全称是Block Formatting Context,即块格式化上下文。它是 CSS2.1 规范定义的,关于 CSS 渲染定位的一个概念。要明白 BFC 到底是什么,还得首先聊聊 CSS 中的视觉格式化模型

什么是视觉格式化模型、什么是盒子类型

写了这么久Web的我们都知道:块级元素换行、行内元素不换行、浮动和绝对定位脱离文档流等等。 以上都代表了浏览器对某种节点类型的处理方式,这些处理方式就是由视觉格式化模型定义的。

视觉格式化模型,即 Visual Formatting Model,是用来处理文档并将它显示在视觉媒体(浏览器、移动端)上的机制,它也是CSS中的一个概念,或者说,一种规则

那么它是如何工作的呢?

In the visual formatting model, each element in the document tree generates zero or more boxes according to the box model.

首先,视觉格式化模型定义了盒子(Box)这个概念,————文档树中的每个元素,都属于某种盒子。它可用于文档元素的定位、布局和格式化,盒子类型主要包括了:

  1. 块盒子
  2. 行内盒子
  3. 匿名盒子(没有名字且不能被选择器选中的盒子)

这边的术语非常多,而且不能乱用,具体可参考MDN文档 。以下只提取最大级的概念。

CSS的盒子模型就是我们熟知的content-boxborder-box那些,这里就不多展开了。

盒子的类型由元素的 display属性决定:

display: block;//list-item  table
display: inline;//inline-block  inline-table
display: inherit;

块盒子 Block Box

  1. 当元素的 displayblocklist-itemtable 时,该元素将成为块级元素( block-level element )。
  2. 每个块级元素都会至少生成一个块级盒子( block-level box )。
  3. 每个块级盒子都会参与块格式化上下文的创建,即BFC。
  4. 还有一种类型:块容器盒子( block container box ):它要么只包含其他块级盒子,要么只包含行内盒子并同时创建一个行内格式化上下文,即IFC。。
  5. 块盒子 = 既是块级盒子又是块容器盒子的盒子。

简单理解:每个块级元素至少生成一个块级盒子,参与BFC,在视觉上表现为:呈现为块,竖直排列。

行内盒子 Inline Box

  1. 如果一个元素的 display属性为 inlineinline-blockinline-table,则称该元素为行内级元素( inline-level element )。
  2. 行内级元素生成行内级盒子 ( inline-level box )。
  3. 行内级盒子可能会参与行内格式化上下文( inline formatting context )的创建,即IFC。
  4. 行内盒子 = 参与了行内格式化上下文创建的行内级盒子
  5. 原子行内级盒(atomic inline-level boxes) = 没参与IFC创建的行内级盒子,如displayinline-blockinline-table

简单理解:行内级元素会生成行内盒子,参与IFC,在视觉上表现为:将内容与行内级元素排列为多行;例如包含文本、图片等多种行内级元素的段落。

匿名盒子 Anonymous Box

  1. 在某些情况下进行视觉格式化时,需要添加一些增补性的盒子,这些盒子不能用 CSS 选择符选中,因此称为匿名盒子
  2. 此时它的所有可继承的 CSS 属性值都为 inherit
  3. 它也分为匿名块盒与匿名行内盒

简单理解:某些元素(如纯文本元素),在视觉呈现的时候,会自动创建出一个不能用CSS选择符选中的盒子,就称这个盒子为匿名盒子。

【4.5k字】最全面的BFC梳理,再也不怕面试官深究了!(含Demo)

浏览器根据盒子类型形成定位方案

浏览器在进行视觉呈现的时候,主要步骤分为:生成盒子 -> 形成定位方案 -> 完成布局。

定位方案指的就是定位盒子的方案,它又分为三种:

  1. 普通流。
  2. 浮动。
  3. 绝对定位。

普通流 Normal flow

  1. 当 CSS 的 position 属性为 staticrelative,并且 floatnone 时,其布局方式为普通流。
  2. 在普通流中,会按照次序依次定位每个盒子:
    1. 在 BFC 中,盒子在垂直方向依次排列。
    2. 在 IFC 中,盒子在水平方向依次排列
  3. positionstatic 时,为静态定位,盒子位置 = 普通流布局中位置。
  4. positionrelative 时,为相对定位,盒子位置 = 普通流布局中位置 + topbottomleftright 各个属性产生的偏移量。但仍然占据原有的空间 ,其它常规流不能占用这个位置

浮动 Floats

  1. 一个盒子的 float 值不为 none,并且其 positionstaticrelative 时,该盒子为浮动定位。
  2. 在浮动定位中,盒子会浮动到当前行的开始或尾部位置。
  3. 其他普通流定位的盒子会环绕在它的周边,除非清除浮动

绝对定位 Absolute positioning

  1. 如果元素的 positionabsolutefixed,该元素为绝对定位。
  2. 对于 position: absolute,元素定位将相对于最近的一个 relativefixedabsolute的父元素,如果没有则相对于 body
  3. 对于 position: fixed,元素定位将相对于屏幕视口(viewport)的位置,且不会因为屏幕滚动而改变。
  4. 在绝对定位中,盒子会完全从当前常规流中移除,并不占据原有的空间,
  5. 盒子位置 = 定位起点 + topbottomleftright 各个属性产生的偏移

回到块格式化上下文

经过上面概念的梳理,我们现在知道什么是 BFC 了————它是 Web 页面 CSS 视觉渲染的一部分, 用于决定块盒子的布局 以及 浮动相互影响范围 的一个区域 。

有一句话我觉得概括得很好———— BFC 就是 CSS 里的块级作用域。总之它是一个范围、一个框、一个区域。

哪些元素会生成BFC:

  1. 根元素。即HTML元素。
  2. 浮动元素float的值不为none
  3. 绝对定位元素position的值为absolutefixed
  4. overflow的值不为visible
  5. 行内块元素或者表格单元格display:inline-blocktable-celltable-caption
  6. 弹性盒元素display: flexinline-flex

最常见的是 overflow:hiddendisplay: flexfloat:left/rightposition:absolute。也就是说,每次看到这些属性的时候,就代表了该元素已经创建了一个 BFC 了。

BFC 的范围

BFC 的范围在 MDN 中是这样描述的。

A block formatting context contains everything inside of the element creating it that is not also inside a descendant element that creates a new block formatting context.

意思是一个 BFC 包含创建该上下文元素的所有子元素,但不包括创建了新 BFC 的子元素的内部元素。

这段看上去有点奇怪,我是这么理解的,加入有下面代码,class名为 .BFC代表创建了新的 BFC :

<div id='div_1' class='BFC'>
    <div id='div_2'>
        <div id='div_3'></div>
        <div id='div_4'></div>
    </div>
    <div id='div_5' class='BFC'>
        <div id='div_6'></div>
        <div id='div_7'></div>
    </div>
</div>

这段代码表示,#div_1创建了一个块格式上下文,这个上下文包括了 #div_2#div_3#div_4#div_5。即 #div_2中的子元素也属于 #div_1所创建的BFC。但由于 #div_5创建了新的BFC,所以 #div_6#div_7就被排除在外层的BFC之外。

我认为,这从另一方角度说明, 一个元素不能同时存在于两个BFC中

BFC 的一个最重要的效果是,让处于 BFC 内部的元素与外部的元素相互隔离,使内外元素的定位不会相互影响

所以,如果一个元素能够同时处于两个 BFC 中,那么就意味着这个元素能与两个 BFC 中的元素发生作用,就违反了 BFC 的隔离作用,所以这个假设就不成立了。

BFC 的布局规则:

  1. 内部的盒子会在垂直方向上一个接一个的放置。(相当于内部也有一个常规流)
  2. 盒子垂直方向的距离由margin决定。属于同一个 BFC 的两个相邻盒子margin会发生重叠(即margin塌陷)。
  3. 每个元素的margin左边界,都与容器块左边界相接触。即使是浮动元素也是如此。
  4. BFC 就是页面上的一个隔离的独立容器,容器里面的子元素不会影响到外面的元素,反之亦然。
  5. 计算 BFC 的高度时,考虑 BFC 所包含的所有元素,连浮动元素也参与计算。
  6. 浮动盒区域不叠加到 BFC 上。

看到以上的几条约束,想想我们学习 CSS 时的几条规则

  1. 块级元素会扩展到与父元素同宽,所以块级元素会垂直排列。
  2. 垂直方向上的两个相邻块级元素的margin会塌陷,而水平方向不会。
  3. 浮动元素会尽量接近往左上方(或右上方)。
  4. 为父元素设置 overflow:hiddenfloat:left,则会包含浮动元素。

BFC的作用

  1. 防止margin塌陷,包括垂直相邻塌陷、嵌套元素塌陷。
  2. 清除内部浮动,防止高度塌陷,或者防止浮动元素与其他元素重叠。
  3. 自适应双栏、多栏布局

Demo1:防止margin塌陷

问题描述:

M3C文档里是这样描述margin塌陷的:

In CSS, the adjoining margins of two or more boxes (which might or might not be siblings) can combine to form a single margin. Margins that combine this way are said to collapse, and the resulting combined margin is called a collapsed margin.

在一个 BFC 里,两个或多个相邻盒子的上外边距和下外边距可能会塌陷(折叠)为一个外边距,其大小会取其中外边距值大的那个,这种现象就是margin塌陷。需要注意的是,浮动的元素绝对定位这种脱离文档流的元素的外边距不会折叠,因为他们属于其他两种的定位方案margin塌陷只会出现在垂直方向

W3C文档对于 “相邻”( 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 (Note that certain zero-height line boxes (see 9.4.2) are ignored for this purpose.)
  • 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.

简单概括:两者属于同一个 BFC 且它两之间没有行盒子,没有间隙,没有padding也没有border,那他们之间就属于相邻

计算原则:

出现margin塌陷时合并外边距的计算原则如下: ● 如果两者都是正数,那么就取最大值。 ● 如果是一正一负,就会取正值减去负值的绝对值。 ● 两个都是负值时,用 0 减去两个数中绝对值大的那个。

问题1:兄弟之间发生margin塌陷

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <style>
      * {
        margin: 0;
        padding: 0;
      }
      .first {
        margin: 100px;
        background: lightgreen;
        border: 1px solid;
        width: 100px;
        height: 100px;
      }
      ul {
        /* display: inline-block; */
        /* position: fixed; */
        /* float: left; */
        margin: 100px;
        background: lightblue;
        border: 1px solid;
        width: 200px;
      }
      li {
        margin: 10px 20px;
      }
    </style>
  </head>

  <body>
    <div class="first"></div>
    <ul>
      <li>item1</li>
      <li>item2</li>
      <li>item3</li>
    </ul>
  </body>
</html>

如图,以上代码创建两个相邻的块级元素,并都将他们的margin设置为100px。可以观察到两个块级元素发生marin塌陷【4.5k字】最全面的BFC梳理,再也不怕面试官深究了!(含Demo)

解决办法:

1.利用绝对定位、浮动盒子、inlne-block盒子都不会发生margin塌陷的特性。参考CSS2文档

  • Margins between a floated box and any other box do not collapse (not even between a float and its in-flow children).
  • Margins of elements that establish new block formatting contexts (such as floats and elements with 'overflow' other than 'visible') do not collapse with their in-flow children.
  • Margins of absolutely positioned boxes do not collapse (not even with their in-flow children).
  • Margins of inline-block boxes do not collapse (not even with their in-flow children).

设置下方元素float:leftposition:absolute使下方元素脱离普通流,或设置'display:inline-block'使其成为inline-block盒子。

注意:如果此时只是给<ul>设置overflow:hiddendisplay:flexdisplay:table-cell等,使其生成 BFC 都不会发生作用。因为此时虽然对内已经生成了BFC,但是对外它还是为一个块级盒子,两相邻块级盒子之间就是会margin塌陷回去看看“相邻”的定义

Vertical margins between adjacent block-level boxes in a block formatting context collapse. 【4.5k字】最全面的BFC梳理,再也不怕面试官深究了!(含Demo)

2.在底部元素外部包裹一个新的元素,使其生成 BFC 。利用margin塌陷只会发生在同一个 BFC 下的两相邻块级盒子之间的特性。 【4.5k字】最全面的BFC梳理,再也不怕面试官深究了!(含Demo)

问题2:父子之间发生margin塌陷

其实上面说的,相邻兄弟块级盒子之间会发生margin塌陷是有利于我们实际开发的,一般可以不修改,而这个发生在父子之间的,几乎就是必改的了。

...
ul {
        /* display: inline-block; */
        /* position: fixed; */
        /* float: left; */
        margin: 100px;
        background: lightblue;
        /* 注释下一行,会发生父子间margin塌陷 */
        /* border: 1px solid; */
        width: 200px;
    }

如代码,我们注释掉<ul>元素的boder,此时会发生父子块级盒子之间的margin塌陷。如图,我们给每个<li>设置了margin,但是会发现此时<div><ul>之间的垂直距离,取<div>、<ul>、<li>三者之间的最大外边距,为100px

【4.5k字】最全面的BFC梳理,再也不怕面试官深究了!(含Demo)

为什么注释掉<ul>元素的boder就会引起这个现象呢??因为注释掉的话,<div><li>之间就没有任何阻隔了,根据上面提到的相邻两个或多个盒子之间就会产生margin塌陷的规则,就会出现这个现象。回去看看“相邻”的定义

解决方法:
  1. <ul>添加boderpadding等属性,使<div>、<ul>、<li>三者不满足“相邻”条件。 【4.5k字】最全面的BFC梳理,再也不怕面试官深究了!(含Demo)

2.使<ul>内部变成 BFC,利用 BFC 之间互不影响的特性。

【4.5k字】最全面的BFC梳理,再也不怕面试官深究了!(含Demo)

Demo2:防止高度塌陷或防止浮动元素与其他元素重叠。

问题1:高度塌陷

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title></title>
      <style>
    * {
        margin: 0;
        padding: 0;
      }

    .parent {
        border: 2px solid black;
        width: 400px;
        /* overflow: hidden; */
    }
 
    .child1 {
        float: left;
        background-color: lightblue;
        width: 100px;
        height: 300px;
    }
    .child2 {
        background-color: lightgreen;
        width: 300px;
        height: 100px;
    }
</style>
<body>
    <div class="parent">
        <div class="child1"></div>
        <div class="child2"></div>
    </div>
</body>

</html>

如图,以上代码在一个父<div>里创建了两个子<div>,并将第一个子<div>设置为浮动。可以观察到父<div>的高度并没有计算浮动子<div>的高度,发生了高度塌陷

【4.5k字】最全面的BFC梳理,再也不怕面试官深究了!(含Demo)

解决办法:

使父<div>生成 BFC 即可,利用:BFC 计算高度时,考虑 BFC 所包含的所有元素(不只是子元素),连浮动元素也参与计算这一特性。就如上一张图中的HTML元素,因为它也是BFC,所以能计算到浮动元素的高度。

当然清除浮动也可以使用一个包含clear:both的空元素,或者设置父<div>的伪元素:afterdisplay:block;clear:both来实现,相比BFC,设置伪元素效果更好,更多人使用。

【4.5k字】最全面的BFC梳理,再也不怕面试官深究了!(含Demo)

问题2:浮动元素与其他元素重叠

此时,观察图片,我们还发现两个子<div>之间还会发生重叠,这是因为浮动元素会脱离普通流并不占据原本空间,而其他普通流定位的盒子会环绕在它的周边

【4.5k字】最全面的BFC梳理,再也不怕面试官深究了!(含Demo)

解决办法:

使发生重叠的子<div>生成 BFC 即可,利用:浮动盒区域不叠加到 BFC 上这一特性。

【4.5k字】最全面的BFC梳理,再也不怕面试官深究了!(含Demo)

Demo:自适应双栏、三栏布局。

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>自适应双栏布局和三栏布局</title>
    <style>
        * {
            margin: 0;
            padding: 0;
        }

        .aside {
            float: left;
            width: 100px;
            min-height: 300px;
            background: lightcoral;
        }

        .main {
            height: 200px;
            background: lightgreen;
            display: flex;
            overflow: hidden;
        }

        .left {
            background: pink;
            float: left;
            width: 180px;
        }

        .center {
            background: lightyellow;
            overflow: hidden;

        }

        .right {
            background: lightblue;
            width: 180px;
            float: right;
        }

        .BFC {
            overflow: hidden;
        }

        .item {
            width: 50px;
            height: 50px;
            margin: 20px;
            background-color: lightgray;

        }
    </style>


</head>

<body>

    <section class="BFC">
        <div class="aside">我是aside</div>
        <div class="main">
            <div class="item"></div>
            <div class="item"></div>
            <div class="item"></div>
        </div>
    </section>

    <section class="BFC" style="margin-top: 100px;">
        <div class="container">
            <div class="left">
                <pre>
      .left{
        background:pink;
        float: left;
        width:180px;
      }
          </pre>
            </div>
            <div class="right">
                <pre>
      .right{
        background:lightblue;
        width:180px;
        float:right;
      }
          </pre>
            </div>
            <div class="center">
                <pre>
      .center{
        background:lightyellow;
        overflow:hidden;
        height:116px;
      }
          </pre>
            </div>

    </section>
</body>

</html>

原理上文已经说过了,这里就不再重复了,我直接写在图中。 【4.5k字】最全面的BFC梳理,再也不怕面试官深究了!(含Demo) 【4.5k字】最全面的BFC梳理,再也不怕面试官深究了!(含Demo)

总结

至此,我们对BFC的学习就结束啦。俺最大的感受还是觉得有些东西还得靠自己积累和查阅,而不是人云亦云。

如果恰好对你们有所帮助的话请不要吝啬你们的点赞和收藏哦~谢谢。