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

前言

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

前些天被人问到了 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属性决定:

css 复制代码
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选择符选中的盒子,就称这个盒子为匿名盒子。

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

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

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

  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 :

bash 复制代码
<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塌陷

xml 复制代码
<!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塌陷

解决办法:

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.

2.在底部元素外部包裹一个新的元素,使其生成 BFC 。利用margin塌陷只会发生在同一个 BFC 下的两相邻块级盒子 之间的特性。

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

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

css 复制代码
...
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

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

解决方法:
  1. <ul>添加boderpadding等属性,使<div>、<ul>、<li>三者不满足"相邻"条件。

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

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

问题1:高度塌陷

xml 复制代码
<!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>的高度,发生了高度塌陷

解决办法:

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

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

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

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

解决办法:

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

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

xml 复制代码
<!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>

原理上文已经说过了,这里就不再重复了,我直接写在图中。

总结

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

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

相关推荐
百万蹄蹄向前冲5 分钟前
Trae分析Phaser.js游戏《洋葱头捡星星》
前端·游戏开发·trae
朝阳58143 分钟前
在浏览器端使用 xml2js 遇到的报错及解决方法
前端
GIS之路1 小时前
GeoTools 读取影像元数据
前端
ssshooter1 小时前
VSCode 自带的 TS 版本可能跟项目TS 版本不一样
前端·面试·typescript
Jerry2 小时前
Jetpack Compose 中的状态
前端
dae bal3 小时前
关于RSA和AES加密
前端·vue.js
柳杉3 小时前
使用three.js搭建3d隧道监测-2
前端·javascript·数据可视化
lynn8570_blog3 小时前
低端设备加载webp ANR
前端·算法
LKAI.3 小时前
传统方式部署(RuoYi-Cloud)微服务
java·linux·前端·后端·微服务·node.js·ruoyi
刺客-Andy4 小时前
React 第七十节 Router中matchRoutes的使用详解及注意事项
前端·javascript·react.js