Blog A Paranoid Guy

BFC(块级格式化上下文)

2016-06-07
CSS

这几天研究了 W3C 的官方文档,想寻找些思路,提高一下 CSS 水平,先从 BFC 说起吧:

常规流

常规流中的盒属于一个格式化上下文,可能是块或是内联,但不能都是(既是块又是内联)。块级盒参与块格式化上下文,内联级盒参与内联格式化上下文

本文讨论 BFC (块级格式化上下文),暂不讨论 IFC (内联格式化上下文)

块级格式化上下文

BFC 被新建立,满足下列任一条件即可:

  • 浮动元素(确切的说除了 none 以外的值)

  • 绝对定位元素

  • 非块盒的块容器( inline-block , table-cell , table-caption

  • overflow 不为 visible 的块盒

在一个块格式化上下文中,盒在竖直方向一个接一个地放置,从包含块的顶部开始。两个兄弟盒之间的竖直距离由 margin 属性决定。同一个块格式化上下文中的相邻块级盒之间的竖直 margin 会合并

在一个块格式化上下文中,每个盒的 left 外边挨着包含块的 left 边(对于从右向左的格式化, right 边挨着)。即使存在浮动(尽管一个盒的行盒可能会因为浮动收缩),这也成立。除非该盒建立了一个新的块格式化上下文(这种情况下,该盒自身可能会因为浮动变窄)

BFC的应用

解决外边距叠加

那么,什么是外边距叠加呢?

在CSS中,两个或两个以上的盒子的外边距(可能是也可能不是相邻元素)可能结合成单个的外边距。这种现象称之为 折叠 ,结果就是合并的外边距称之为 折叠外边距(collapsed margin)

关于外边距叠加的条件可以看我的这篇译注,还有官方英文文档

BFC 解决外边距叠加举个例子吧,这里引用的是 W3C 文档中解释 BFC 的例子,如图所示:

上图只是一种假设的情况,实际是不存在的:

假设块 B1margin-bottomM1B1 没有子级也没有 paddingborder ),浮动块 F 的高度为 H,块 B2margin-topM2(没有 paddingborder 也没有子级),B2clearboth。同样假设 B2 非空, 不考虑 B2clear 属性,情况如图所示,B1B2margin 合并了。B1border-bottom 边在 y = 0 处,Ftopy = M1 处,B2border-top 边在 y = max(M1,M2) 处,Fbottomy = M1 + H

实际的情况是这样的:

源代码如下:

    <!DOCTYPE html>
        <html>
        <head>
            <title>BFC模型</title>
            <style type="text/css">
                html, body {
                    margin: 0;
                }
                .b1 {
                    margin-bottom: 50px;
                    height: 100px;
                    width: 200px;
                    background: #f3f3f3;
                }
                .f {
                    float: left;
                    height: 100px;
                    width: 100px;
                    background: #bfbfbf;
                }
                .b2 {
                    margin-top: 10px;
                    height: 100px;
                    width: 200px;
                    background: #f3f3f3;
                }
            </style>
        </head>
        <body>
            <div class="b1">B1</div>
            <div class="f">F</div>
            <div class="b2">B2</div>
        </body>
    </html>

通过浏览器的开发者工具可以清晰的看到,B1margin-bottomB2margin-top 合并了,于是我们想,怎么样解决合并?W3C 文档提供了解决这方面的方法,之一就是:

一个正常流元素的 margin-bottom 与它下一个正常流的兄弟元素的 margin-top 会产生折叠,除非它们之间存在空隙(clearance

这个 空隙 是什么呢?我们暂且后面再讨论,先为上面的代码 B2CSS 属性添加一行

    .b2 {
        clear: left;
    } 

于是

我们再通过开发者工具查看,发现——外边距合并消失了!

其实这就是通过 clear 引用了一个 空隙,具体说下 空隙

先看 clear 属性:

  • left

要求该盒的 border-top 边位于源文档中在此之前的元素形成的所有左浮动盒的 bottom 外边下方

  • right

要求该盒的 border-top 边位于源文档中在此之前的元素形成的所有右浮动盒的 bottom 外边下方

  • both

要求该盒的 border-top 边位于源文档中再次之前的元素形成的所有左浮动盒和右浮动盒的 bottom 外边下方

  • none

对该盒相对于浮动(盒)的位置没有约束

值不为 none 就隐含了要引入空隙(clearance)。空隙会阻止 margin 合并,并作为元素 margin-top 上方的空间。用来在竖直方向上推着元素越过浮动

计算一个设置了 clear 的元素的空隙,先要确定该元素的 border-top 边的假定位置。该位置是当元素的 clear 属性为 none 时其 border-top 边实际所在的位置

如果该元素的 border-top 边的假定位置没有越过相关的浮动(盒),那么就得引入空隙,并根据相关规则合并 margin

然后,空隙的高度被设置为下面两者的较大值:

  1. 把块的 border-top 边放在最低的将被 clear 的浮动(盒)的 bottom 外边,所必需的高度

  2. 把块的 border-top 边放在其假定位置( clearnone 时的位置),所必需的高度

或者,空隙被精确地设置为把块的 border 边放在最低的将被 clear 的浮动(盒)的 border-bottom 外边,所必需的高度

注意:空隙可以为负或者为 0

回到最上面的假设,计算一下这个 空隙 具体是多少呢?这里要计算两次 C1C2 ,并取最大值 C

  • 第一次计算

既满足上面 条件1 的情况。

    F(border-bottom) = B2(border-top) -> M1 + H = M1 + C1 + M2 -> C1 = H - M2

解释就是:B2 的顶部放在浮动块 F 的底部的位置等于 B1border-bottom 加上 空隙 加上 B2border-top

  • 第二次计算

既满足上面 条件2 的情况。

    max(M1, M2) = M1 + C2 + M2 -> C2 = max(M1, M2) - M1 -M2

解释就是: 把 B2 放在假定位置等于B1border-bottom 加上 空隙 加上 B2border-top

假设 max(M1, M2) < M1 + H , 再次推导:

    C2 = max(M1, M2) - M1 - M2 < M1 + H - M1 - M2 = H - M2 -> C2 < H - M2

也即 C2 < C1

所以 C = max(C1, C2) = C1

根据以上推导,得出以下结论:

  • 如果 B1margin-bottomB2margin-top 的最大值小于 B1margin-bottom 加上 F 的高度,那么 空隙 C 就是 F 的高度减去 B2margin-top

  • 如果 B1margin-bottomB2margin-top 的最大值大于 B1margin-bottom 加上 F 的高度,那么 空隙 C 就是 B1margin-bottom 或者 B2margin-top 的值中最小的一个

说实话,我总结的自己都觉得绕啊(+﹏+)~

解决一些布局问题

如图,当我们没有对 DIV2 清楚浮动影响时的情况,这是因为 DIV1 为浮动元素并且重新建立了 BFC ,而 DIV2 仅处于常规流的 BFC 中。

这就需要我们为 DIV2 重新建立 BFC,例如添加 overflow: hidden , 其它方法也可(设置为非块盒的块容器,不推荐),之后如下图:

总结

其实 BFC 的应用很广,再次不在过多概述。作为一个前端开发人员,了解原理是非常重要的,我们以后遇到什么问题,都可以由此联想到这里。

送给大家一句话,“前端是坑,上手容易,精通不易” 。当然我不是讽刺,而是说前端这个行业要想做好,不能只停留在表面。要想成为大牛,还是要花些时间深入研究,花费的时间将是以后少走弯路的指南针!各位前端的朋友们,一起努力加油吧!


上一篇 Java IO流接口

下一篇 Compass初体验

Comments