Skip to content

css样式中BFC的理解 #4

@lizhik

Description

@lizhik

筒子们,在我们实际的工作当中我们经常会遇见一些头疼的样式问题,有些问题也许是各种尝试才能意外得到想要的效果,并不能理解缘由,之前对于CSS样式清除内部元素浮动可能会用clearfix,但我并不理解这里面哪个属性起到了怎样的作用,这两天看到了关于BFC的含义和应用,好像发现了些自身css原理上的不足,跟大家做个查阅与分享,让我们先来了解几个概念:

 块级盒block-level box,它是参与了块级排版上下文的一种盒子,每个块级元素都生成了一个包含后代盒子和生成的内容的主要块级盒,并且这个盒子参与了任何定位的计算(块级元素是那种源文档被格式化为可视块了的元素,然后使这个元素变成块级元素的display属性取值如下: ‘block’, ‘list-item’, 和 ‘table’)。

 什么是BFC?BFC(Block formatting context)直译为"块级格式化上下文"。它是一个独立的渲染区域,只有Block-level box参与, 它规定了内部的Block-level Box如何布局,并且与这个区域外部毫不相干。

BFC布局规则:

  • 内部的Box会在垂直方向,一个接一个地放置。

  • Box垂直方向的距离由margin决定。属于同一个BFC的两个相邻Box的margin会发生重叠

  • 每个元素的margin box的左边, 与包含块border box的左边相接触(对于从左往右的格式化,否则相反)。即使存在浮动也是如此。

  • BFC的区域不会与float box重叠。

  • BFC就是页面上的一个隔离的独立容器,容器里面的子元素不会影响到外面的元素。反之也如此。

  • 计算BFC的高度时,浮动元素也参与计算。

    哪些元素会生成BFC,可以通过设置对应属性解决布局问题:

  • 根元素

  • float属性不为none

  • position为absolute或fixed

  • display为inline-block, table-cell, table-caption, flex, inline-flex

  • overflow不为visible
    记住相关概念下面让我们一起理解一下,先看一下我们熟知的盒模型:
    image
    块级元素会自动生成一个块级盒block-level box,这是块级盒block-level-box的盒模型构成,它表明的是块级盒自身的结构构成。margin、border、padding、content分别定义了元素四种边,然后每种类型的边的四条边定义了一个盒子,分别是content box、padding box、border box、margin box,而决定块盒在包含块中与相邻块盒的垂直间距的便是margin-box,这个margin-box是始终存在的,即使它的margin为0。如下例:
    <div style="width: 400px;height: 400px; background:#ff4400;"></div>

    看一下解析效果:
    image
    上面说了元素的四个margin的默认值都是0(见红色部分),然后应用对象是所有的元素(见蓝色部分),所有元素当然包括块级元素,因此所有的块级盒子无论怎样都会有一个margin-box,在BFC中,margin-box会与其相邻的margin-box的对边相折叠(关于BFC的margin折叠这里先不讨论)。margin-box是参与块级盒在上下文中的布局的,但是参与BFC布局的盒子却是块级盒block-level box,并且还有一点需要明确的是参与布局的是盒子而不是元素本身。下面这张图是块级盒block-level box的构成:

image
那么block-level box在页面中是怎样布局的呢。
我们通常的描述是这样的:

普通流中的块元素独占一行,然后从上往下一个接一个的排布,相邻元素间会有外边距折叠。

官方文档解释:

大致意思如下:第一段:浮动元素、绝对定位元素,不是块级盒的块级包含块(比如inline-block、table-cell、table-capation)和overflow值不为visible的块级盒子为它们的内容建立了一个新的块级排版上下文。

第二段:在一个块级排版上下文中,盒子是从包含块顶部开始,垂直的一个接一个的排列的,相邻两个盒子之间的垂直的间距是被margin属性所决定的,在一个块级排版上下文中相邻的两个块级盒之间的垂直margin是折叠的。参与BFC的布局的只有普通流normal flow中的块级盒,而float、position值不为relative\static的元素是脱离BFC这一布局环境的,不参与BFC的布局

第三段:在一个块级排版上下文中,每个盒子的左外边是触碰到包含块的左边的(对于从右向左的排版,则相反),即使在有浮动元素参与的情况下也是如此(即使一个盒子的行盒是因为浮动而收缩了的),除非这个盒子新建了一个块级排版上下文(在某些情况下这个盒子自身会因为floats而变窄)。

先看第二段和第三段话,然后我们再来看一个很常见的实例: 代码如下:

<div style="width: 200px;height: 200px;background: #cccccc;"></div>
<div style="width: 200px;height: 200px;background: #333333;"></div>
<div style="width: 200px;height: 200px;background: #666666;"></div>

结果如下:
image

三个块级盒,从上往下排列,看样子是遵循如上面第二段所述的这一条BFC布局规则的:“在一个块级排版上下文中,盒子是从包含块顶部开始,垂直的一个接一个的排列的”,那么这里的盒子要从上往下排列的话,肯定是得要一个包含块containing block和一个块级排版上下文BFC的,:root元素是会在其下创建一个BFC的,那么在这个BFC下的所有块级盒都是会在它的包含块中垂直的一个接一个的排布的。元素盒子的位置和尺寸往往是根据一个矩形计算出来的,我们称这个矩形为元素的包含块

如果这个元素的position值是relative或static,这个元素的包含块是由离其最近的块级的祖先盒子的内容的边content-edge构成的。就是离其最近的块级祖先盒子的content-box。

搞清了包含块containing block的概念,再来看看这个实例中的3个div的包含块,很显然是其父级元素body,同时还在canvas所设立的BFC下,按照“在一个块级排版上下文中,盒子是从包含块顶部开始,垂直的一个接一个的排列的”的这个定义,我们已经确定了,这里的div是会垂直的一个接一个的排列的,但是你要注意到,第二段话只是定义了垂直方向的排布规则,还没说水平方向的,那么水平方向的又如何呢,试看第三段话的前两句:“在一个块级排版上下文中,每个盒子的左外边是触碰到包含块的左边的(对于从右向左的排版,则相反)”,而在这里,我尚未为其定义从右向左的排版(对于从右向左的排版,所以这个div的左外边是会触碰到包含块body的左边的。

再来看上面那段话第二段中的一句话:“相邻两个盒子之间的垂直的间距是被margin属性所决定的,在一个块级排版上下文中相邻的两个块级盒之间的垂直margin是折叠的”,这是参与BFC布局的块级盒的又一特性,试看以下实例:

代码:

<div style="width: 200px;height: 200px;background: #cccccc;margin-bottom: 30px;"></div>
<div style="width: 200px;height: 200px;background: #333333;margin-top: 30px;margin-bottom:30px;">   </div>
<div style="width: 200px;height: 200px;background: #666666;margin-top: 50px;"></div>

如图:
image

可以看到上下两个div的margin折叠了,第一个div的margin-bottom和第二个div的margin-top折叠为了30px,第二个div的margin-bottom和第三个div的margin-top折叠为了50px,而这个折叠本质是块级盒block-level box下的margin-box的折叠,后面会讲到如果再到这几个div的外面一层再包裹一个拥有BFC的元素的话,他们之间的margin便不会折叠了,因为BFC里面的盒子和其外面的盒子间是不会有任何影响的,你可能会疑惑那这里的三个div不是也在:root所设立的BFC下吗,那为什么还会折叠,原因很简单,就是因为你那个BFC在:root那里去了,BFC相当于一堵墙,你这个墙在这里应该在每个div的外面才会起到隔离这几个div的作用啊,而:root下的那个BFC则是隔离的:root下的直接子元素了。

上面说的都是:root下的BFC。那么,假设你在不知道比如float的这些特性能用于创建BFC的时候,你会不会很好奇的去想:root下的盒子会不会也有可以用于创建BFC的方法与对应的盒子呢?上下文套上下文可是一件很令人感到愉悦的事情,因为我可以把那个能创建BFC的盒子当作那个:root,而这个创建了BFC的盒子则是一个独立的容器,里面的参与BFC的块级盒不会影响到盒子外面的盒子,外面的盒子也不会影响到里面参与了BFC的块级盒。

代码如:

 <div style="overflow: auto;width: 100%;">
    <div style="float: left;width: 100%;">
        <div style="margin-bottom: 30px;width: 100px;height: 100px;background-color: #cccccc;"></div>
    </div>
    <div style="float: left;width: 100%;">
        <div style="margin-top: 50px;width: 100px;height: 100px;background-color: #333333;"></div>
    </div>
</div>

image

可以看到,上面示例中的上面灰色div和下面黑色div的margin并没有重叠,这是因为那两个float的父盒子在为它下面的盒子创建了一个BFC,从而将float盒子里面的子盒子给隔离了起来,因此也就不会margin折叠了,这只是创建BFC的一个方法,其它的还有overflow:hidden等,而在这个BFC下的盒子都是遵循BFC的布局规则的。

然后我们来从盒模型的角度来理解下,BFC是如何将其下的盒子与外界隔离起来的,首先,最基本的盒子构成我们上面已经说过了,见上面的描述block-level box的构成的那张图:

然后,当块级盒block-level box外层没有BFC作保护,有margin折叠时,是这样的:
image

而当块级盒block-level box外层有BFC作保护时(比如给下图灰色边框线盒子一个float:left),则是这样的:

同时BFC下的盒子是按照BFC的规则从上往下一个接一个的排列,并且存在外边距折叠的,你可以通过在这层BFC下再去嵌套BFC来阻止下面盒子的外边距折叠
Uploading image.png…

最后,我们通过一张图来了解一个页面中的BFC的构成(有红色虚线的代表的是拥有BFC的元素):
Uploading image.png…

 因为BFC内部的元素和外部的元素绝对不会互相影响,因此, 当BFC外部存在浮动时,它不应该影响BFC内部Box的布局,BFC会通过变窄,而不与浮动有重叠。同样的,当BFC内部有浮动时,为了不影响外部元素的布局,BFC计算高度时会包括浮动的高度。避免margin重叠也是这样的一个道理。

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions