《架构师重学前端》什么是BFC

1,027 阅读9分钟

提出问题

margin溢出

现象

<style>
    main {
        background-color: #95a5a6;
        width: 200px;
        height: 200px;
    }

    div {
        background-color: #2ecc71;
        width: 100px;
        height: 100px;
        margin-top: 20px;
    }
</style>

<main>
    <div></div>
</main>

可以先想象一下上面代码所渲染出的页面是怎样的?

是不是会渲染成这样

image-20240609180901097

那可大错特错了,渲染结果出乎意料

image-20240609180203162

div元素margin-top样式并没有在main元素内部渲染,而是在main外部渲染了

之所以会出现margin溢出,是因为子元素的外边距在父元素中找不到参照,简单来说,就是子元素不知道外边距要顶着谁,所以就一直往上顶

这是浏览器渲染普通流块元素的BUG之一,在很多教程中,都称此现象为margin塌陷,但是个人感觉称为margin溢出更为合理,只要方便记忆就行

特定

发生的位置

发生在普通流块父子元素之间

注:块元素指的是显示类型是块元素dispaly:bolck;

注:HTML文档流包含普通文档流、浮动文档流、定位文档流

块元素之间

借用上面的代码,修改一下main元素的现实类型display: inline-block;

main {
    display: inline-block;
}

再次运行代码,会发现盛世如我所愿呀

因为main元素不是普通流块元素了,就不符合margin溢出的出现条件

image-20240609180901097

元素普通流

修改了main元素的HTML文档流方式position: absolute;

 main {
     position: absolute;
}

结果不言而喻

image-20240609180901097

传递性

上文说过,margin溢出发生在普通流块父子元素之间,其实也不是完全对,因为margin溢出可以向上传递

<style>
    main {
        background-color: #95a5a6;
        width: 300px;
        margin-top: 10px;
    }

    article {
        background-color: #d35400;
        width: 200px;
    }


    div {
        width: 100px;
        height: 100px;
        background-color: #2ecc71;
        margin-top: 50px;
    }
</style>
<main>
    <article>
        <div></div>
    </article>
</main>

image-20240609200036388

发现main元素div元素中间隔着article元素,已经是祖孙关系了,还是发生了margin溢出

这是因为div元素的top外边距会一直往上传递,直到顶级元素

发生的方位

边距溢出只会发生在父元素上下位置,至于左右并不会发生边距溢出

<style>
    main {
        background-color: #95a5a6;
        width: 200px;
    }


    div {
        background-color: #2ecc71;
        width: 100px;
        height: 100px;
        margin-top: 20px;
        margin-bottom: 20px;
    }
</style>

<main>
    <div></div>
</main>

image-20240609184342510

外边距竞争

上面只介绍div元素拥有外边距的情况,那如果main元素也设置了外边距,外层边距改显示那个元素喃?还是两个元素边距相加?

<style>
    main {
        background-color: #95a5a6;
        width: 200px;
        margin-top: 50px;
    }


    div {
        background-color: #2ecc71;
        width: 100px;
        height: 100px;
        margin-top: 20px;
    }
</style>

<main>
    <div></div>
</main>

image-20240609185128920

这里把div元素main的顶部外边距互换一下

main {
    margin-top: 20px;
}


div {
    margin-top: 50px;
}

image-20240609185607816

是不是就可以得出结论了

当内外元素都设置外边距时,会选出中间最大那个值作为外层元素的外边距

解决方案

设置间隔

让子元素知道外边距在父元素中有参照

<style>
    main {
        background-color: #95a5a6;
        width: 200px;
        height: 200px;
        /* 方案一 */
        border: 5px solid #000;
        /* 方案二 */
        padding: 5px;
    }

    div {
        background-color: #2ecc71;
        width: 100px;
        height: 100px;
        margin-top: 20px;
    }
</style>

<main>
    <div></div>
</main>

image-20240609201357813

设置border或者padding都可以

BFC

上面讲特性时,讲过修改元素普通流块元素特性,这属于BFC的一小部分,具体内容下文讲

margin重叠

现象

<style>
    main {
        background-color: #95a5a6;
        width: 200px;
    }

    div {
        width: 100px;
        height: 100px;
    }


    .div1 {
        background-color: #2ecc71;
        margin-bottom: 20px;
    }

    .div2 {
        background-color: #f1c40f;
        margin-top: 20px;
    }
</style>

<main>
    <div class="div1"></div>
    <div class="div2"></div>
</main>

还是先猜猜页面渲染结果吧?

image-20240609192840941

肯定不是这样啦!!!现实往往是残酷的

image-20240609192910017

div1元素和div2公用一个margin区域,这是浏览器渲染普通流块元素的BUG之二

特点

发生的位置

发生在普通流块兄弟元素之间

块元素之间

修改div1元素的显示类型display: inline-block;

.div1 {
    display: inline-block;
}

结果就是我们一开始想要的样子

image-20240609192840941

元素普通流

修改了div2元素的HTML文档流方式float: left;

 .div2 {
     float: left;
}

image-20240609203547727

这里发生了浮动元素的高度塌陷,有兴趣可以参考CSS浮动布局

传递性

首先说结论,margin重叠不具有传递性

div1元素外层加上section元素

<main>
    <section>
        <div class="div1"></div>
    </section>
    <div class="div2"></div>
</main>

image-20240609204149254

可能你会觉得上面margin重叠不具有传递性的结论是错的,但并不是这样的

之所以显示上面的结构,是因为margin溢出的传递性造成的

div1元素margin溢出section元素*,就相当在section元素上设置了* margin-bottom: 20px;

如果不信,可以通过设置section元素padding: 1px;去除margin溢出

section {
    padding: 1px;
}

image-20240609203547727

又恢复正常了

发生的方位

margin重叠一样,边距重叠只会发生在父元素上下位置,至于左右并不会发生边距重叠

很容易理解块元素是独占一行,自上而下排列的

外边距竞争

这里margin重叠也是一样的,都是取最大的值作为最后的值

<style>
    main {
        background-color: #95a5a6;
        width: 200px;
    }

    div {
        width: 100px;
        height: 100px;
    }


    .div1 {
        background-color: #2ecc71;
        margin-bottom: 60px;
    }

    .div2 {
        background-color: #f1c40f;
        margin-top: 20px;
    }
</style>

<main>
    <div class="div1"></div>
    <div class="div2"></div>
</main>

image-20240609221934876

当相邻元素都设置外边距时,会选出中间最大那个值作为相邻元素之间的外边距

解决方案

BFC

具体内容下文讲

父元素高度塌陷

有兴趣可以参考CSS浮动布局

image-20240601180235217

BFC解决该浮动缺陷,BFC具体内容下文讲

浮动元素覆盖后置元素

有兴趣可以参考CSS浮动布局

image-20240609223413807

BFC解决该浮动缺陷,BFC具体内容下文讲

BFC是什么

上面4种都可以通过启动BFC解决,是不是很想知道BFC到底是什么?

举个例子,小明家有一亩良田,平时都是耕种小麦,突然有一天,国家要退耕还林了,这一亩良田就回归国家种树

普通流块元素触发BFC就改变该区域浏览器渲染规则,就好比上面的例子一样,良田变成了树林,区域还是那片区域,只是该区域的浏览器渲染规则改变了

Img251804638

普通流块元素区域的渲染规则和开启BFC之后该区域渲染规则是不一样

这也很好理解,为什么开启BFC会解决上面的提出BUG,是因为上面这些BUG只存在于普通流块元素渲染规则中,开启BFC改变了该区域的渲染规则,自然就没有原来的BUG了

好比你从穷光蛋变成了富二代,你原来那些没钱的焦虑都会消失(扎心了。。。)

触发BFC产生规则

BFC的全称是Block Formatting Context,中文叫块级格式化上下文

很容易理解,触发BFC就可以影响该块区域的规则,也格式化了该块区域

image-20240610000641529

  • 开启BFC的区域,是一块独立的区域,隔绝了内部和外部的联系
  • 内部元素、父元素、内层元素三方渲染互不素影响

注意: 父元素和内部元素尽量分为两个元素,要不某些触发BFC的方式不起作用

BFC的规则,可以形容为世外桃源,就是说是隔离的一块区域,内外部互不影响

如何触发BFG

其实通过上面的知识,你也能猜到一些触发BFC的方式吧,既然触发BFC是改变元素区域的渲染规则不是普通流块元素规则,那只需要改变该区域的显示类型(display)、**文档流方式(float、position)**等

  • display属性值为非blocknoneinlinenone属性值是隐藏,设置没有意义)
  • float属性值非none(更改文档流为浮动流)
  • position属性值absolutefixed(更改文档流为定位流,relative属性值并不行)
  • overflow属性值非visible(个人不知道原因,欢迎补充)
  • 浏览器html根元素初始就有BFC
  • 伸缩盒子项目(flex盒子种的子元素)
  • 多列容器(设置column-count属性)
  • column-span属性值为all的元素(表格第一行横跨的所有列)

display: flow-root

其他方式触发语义化,或多或少都会影响代码运行,但是display: flow-root并不会影响,所有建议使用display: flow-root触发BFC,但是IE不兼容

解决问题

margin溢出

<style>
    main {
        background-color: #95a5a6;
        width: 200px;
        height: 200px;
    }

    div {
        background-color: #2ecc71;
        width: 100px;
        height: 100px;
        margin-top: 20px;
    }
</style>

<main>
    <div></div>
</main>
  • div元素触发BFC,让div元素的外边距不影响外部元素

    (不建议,父元素和内部元素尽量分为两个元素,要不某些触发BFC的方式不起作用,再次提醒)

div{
    display: inline-block;
}
  • 触发让main元素触发BFC,让main内部元素不影响外部元素
main{
	display: flow-root;
}

image-20240609180901097

margin重叠

<style>
    main {
        background-color: #95a5a6;
        width: 200px;
    }

    div {
        width: 100px;
        height: 100px;
    }


    .div1 {
        background-color: #2ecc71;
        margin-bottom: 20px;
    }

    .div2 {
        background-color: #f1c40f;
        margin-top: 20px;
    }
</style>

<main>
    <div class="div1"></div>
    <div class="div2"></div>
</main>
  • div元素触发BFC让div元素的外边距不影响外部元素

    (不建议,父元素和内部元素尽量分为两个元素,要不某些触发BFC的方式不起作用,再再次提醒)

    div {
        display: inline-block;
    }
    
  • div1元素和div2元素设置父元素,然后让父元素触发BFC

    <style>
        section {
            display: flow-root;
        }
    </style>
    
    <main>
        <section>
            <div class="div1"></div>
        </section>
        <section>
            <div class="div2"></div>
        </section>
    </main>
    

    image-20240609192840941

父元素高度塌陷

<style>
    main {
        border: 3px solid #34495e;
        padding: 3px;
        background-color: #ecf0f1;
    }

    main>div {
        width: 50px;
        height: 50px;
        background-color: #f1c40f;
        float: right;
    }
</style>
<main>
    <div></div>
</main>
  • main元素触发BFC

    main{
    	display: flow-root;
    }
    

image-20240610001808396

浮动元素覆盖后置元素

<style>
    main {
        border: 3px solid #34495e;
        background-color: #ecf0f1;
    }

    div {
        height: 80px;
    }

    .div1 {
        background-color: #f39c12;
        float: left;
        width: 80px;
    }

    .div2 {
        background-color: #d35400;
        width: 100px;
    }

    
</style>
<main>
    <div class="div1"></div>
    <div class="div2"></div>
</main>
  • div2元素触发BFC让div1元素的浮动不影响外部元素(老规矩,不建议)

    .div2 {
        display: flow-root;
    }
    
  • div1元素和div2元素设置父元素,然后让父元素触发BFC

    <style>
        section {
            display: flow-root;
        }
    </style>
    
    <main>
        <section>
            <div class="div1"></div>
        </section>
        <section>
            <div class="div2"></div>
        </section>
    </main>
    

image-20240610002922146