CSS的BFC与盒子模型

CSS用了很久,但是对于其中的一些用法总是有些模糊,没有一个系统的,有逻辑的认知。这次就先理清楚一下CSS中的BFC与盒子模型,以及相关的一些定位和布局属性。

默认情况下,元素是如何布局的?

首先,取得元素的内容来放在一个独立的元素盒子中,然后在其周边加上内边距、边框和外边距 — 就是我们之前看到的盒子模型。

默认的,一个块级元素的内容宽度是其父元素的 100%,其高度与其内容高度一致。内联元素的 height width 与内容一致。你无法设置内联元素的 height width — 它们就那样置于块级元素的内容里。如果你想控制内联元素的尺寸,你需要为元素设置display: block; (或者,display: inline-block; inline-block 混合了 inline 和 block 的特性。)

这样解释了独立元素的布局,但是元素之间又是如何相互影响的呢? 正常布局流(在布局介绍里提到过)是一套在浏览器视口内放置、组织元素的系统。默认的,块级元素按照基于其父元素的书写顺序(默认值: horizontal-tb) 的*块流动方向 (block flow direction)*放置 — 每个块级元素会在上一个元素下面另起一行,它们会被设置好的 margin 分隔。在英语,或者其他水平书写、自上而下模式里,块级元素是垂直组织的。

内联元素的表现有所不同 — 它们不会另起一行;只要在其父级块级元素的宽度内有足够的空间,它们与其他内联元素、相邻的文本内容(或者被包裹的)被安排在同一行。如果空间不够,溢出的文本或元素将移到新的一行。

如果两个相邻的元素都设置了 margin 并且两个 margin 有重叠,那么更大的设置会被保留,小的则会消失 — 这被称为外边距叠加,我们之前见到过。

BFC是什么

块格式化上下文(Block Formatting Context,BFC)是 Web 页面的可视 CSS 渲染的一部分,是块级盒子的布局过程发生的区域,也是浮动元素与其他元素交互的区域。

下列方式会创建块格式化上下文:

  • 根元素()
  • 浮动元素(float 值不为 none)
  • 绝对定位元素(position 值为 absolute 或 fixed)
  • 行内块元素(display 值为 inline-block)
  • 表格单元格(display 值为 table-cell,HTML 表格单元格默认值)
  • 表格标题(display 值为 table-caption,HTML 表格标题默认值)
  • 匿名表格单元格元素(display 值为 table、table-row、 table-row-group、table-header-group、table-footer-group(分别是 HTML table、tr、tbody、thead、tfoot 的默认值)或 inline-table)
  • overflow 值不为 visible、clip 的块元素
  • display 值为 flow-root 的元素
  • contain 值为 layout、content 或 paint 的元素
  • 弹性元素(display 值为 flex 或 inline-flex 元素的直接子元素),如果它们本身既不是 flex、grid 也不是 table 容器
  • 网格元素(display 值为 grid 或 inline-grid 元素的直接子元素),如果它们本身既不是 flex、grid 也不是 table 容器
  • 多列容器(column-count 或 column-width (en-US) 值不为 auto,包括column-count 为 1)
  • column-span 值为 all 的元素始终会创建一个新的 BFC,即使该元素没有包裹在一个多列容器中 (规范变更, Chrome bug)

格式化上下文影响布局,通常,我们会为定位和清除浮动创建新的 BFC,而不是更改布局,因为它将:

  • 包含内部浮动
  • 排除外部浮动
  • 阻止 外边距重叠

包含内部浮动

让浮动内容和周围的内容等高。也就是说,内部的浮动元素的显示不会超出BFC

为了更好的理解 BFC,我们先看看下面这些内容。

在下面的例子中,我们让 <div>元素浮动,并给它一个 border 效果。<div>里的内容现在已经在浮动元素周围浮动起来了。由于浮动的元素比它旁边的元素高,所以 <div>的边框穿出了浮动。正如我们在 In Flow and Out of Flow 中解释的,浮动脱离了文档流,所以 <div>的 background 和 border 仅仅包含了内容,不包含浮动。

使用 overflow: auto

在创建包含浮动元素的 BFC 时,通常的做法是设置父元素 overflow: auto 或者其它除默认的 overflow: visible 以外的值。<div>元素变成布局中的迷你布局,任何子元素都会被包含进去。

使用 overflow 创建新的 BFC,是因为 overflow 属性会告诉浏览器应该怎样处理溢出的内容。如果使用它仅仅为了创建 BFC,你可能会遇到不希望出现的滚动条或阴影,需要注意。另外,对于后续的开发者,可能不清楚当时为什么使用 overflow,所以最好添加一些注释来解释为什么这样做。

使用 display: flow-root

一个新的 display 属性的值,它可以创建无副作用的 BFC。在父级块中使用 display: flow-root 可以创建新的 BFC。

<div>元素设置 display: flow-root 属性后,<div>中的所有内容都会参与 BFC,浮动的内容不会从底部溢出。

你可以从 flow-root 这个值的名字上看出来,它创建一个新的用于流式布局的上下文,类似于浏览器的根(html)元素。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<section>
<div class="box">
<div class="float">I am a floated box!</div>
<p>I am content inside the container.</p>
</div>
</section>
<section>
<div class="box" style="overflow:auto">
<div class="float">I am a floated box!</div>
<p>I am content inside the <code>overflow:auto</code> container.</p>
</div>
</section>
<section>
<div class="box" style="display:flow-root">
<div class="float">I am a floated box!</div>
<p>I am content inside the <code>display:flow-root</code> container.</p>
</div>
</section>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
section {
height: 150px;
}
.box {
background-color: rgb(224, 206, 247);
border: 5px solid rebeccapurple;
}
.box[style] {
background-color: aliceblue;
border: 5px solid steelblue;
}
.float {
float: left;
width: 200px;
height: 100px;
background-color: rgba(255, 255, 255, .5);
border:1px solid black;
padding: 10px;
}

排除外部浮动

下面的例子中,我们使用 display: flow-root 和浮动实现双列布局,因为正常文档流中建立的 BFC 不得与元素本身所在的块格式化上下文中的任何浮动的外边距重叠。

1
2
3
4
5
6
7
8
<section>
<div class="float">Try to resize this outer float</div>
<div class="box"><p>Normal</p></div>
</section>
<section>
<div class="float">Try to resize this outer float</div>
<div class="box" style="display:flow-root"><p><code>display:flow-root</code><p></div>
</section>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
section {
height:150px;
}
.box {
background-color: rgb(224, 206, 247);
border: 5px solid rebeccapurple;
}
.box[style] {
background-color: aliceblue;
border: 5px solid steelblue;
}
.float {
float: left;
overflow: hidden; /* required by resize:both */
resize: both;
margin-right:25px;
width: 200px;
height: 100px;
background-color: rgba(255, 255, 255, .75);
border: 1px solid black;
padding: 10px;
}

避免外边距重叠

块的上外边距 (margin-top)和下外边距 (margin-bottom)有时合并 (折叠) 为单个边距,其大小为单个边距的最大值 (或如果它们相等,则仅为其中一个),这种行为称为边距折叠。

有三种情况会形成外边距重叠:

同一层相邻元素之间

相邻的两个元素之间的外边距重叠,除非后一个元素加上clear-fix 清除浮动。

没有内容将父元素和后代元素分开

如果没有边框border,内边距padding,行内内容,也没有创建块级格式上下文或清除浮动来分开一个块级元素的上边界margin-top 与其内一个或多个后代块级元素的上边界margin-top;或没有边框,内边距,行内内容,高度height,最小高度min-height或 最大高度max-height 来分开一个块级元素的下边界margin-bottom与其内的一个或多个后代后代块元素的下边界margin-bottom,则就会出现父块元素和其内后代块元素外边界重叠,重叠部分最终会溢出到父级块元素外面。

空的块级元素

当一个块元素上边界margin-top 直接贴到元素下边界margin-bottom时也会发生边界折叠。这种情况会发生在一个块元素完全没有设定边框border、内边距padding、高度height、最小高度min-height 、最大高度max-height 、内容设定为 inline 或是加上clear-fix的时候。

盒子模型

在 CSS 中我们广泛地使用两种“盒子” —— 块级盒子 (block box) 和 内联盒子 (inline box)。这两种盒子会在页面流(page flow)和元素之间的关系方面表现出不同的行为:

一个被定义成块级的(block)盒子会表现出以下行为:

  • 盒子会在内联的方向上扩展并占据父容器在该方向上的所有可用空间,在绝大数情况下意味着盒子会和父容器一样宽
  • 每个盒子都会换行
  • width 和 height 属性可以发挥作用
  • 内边距(padding), 外边距(margin)和 边框(border)会将其他元素从当前盒子周围“推开”

如果一个盒子对外显示为 inline,那么他的行为如下:

  • 盒子不会产生换行。
  • width 和 height 属性将不起作用。
  • 垂直方向的内边距、外边距以及边框会被应用但是不会把其他处于 inline 状态的盒子推开。
  • 水平方向的内边距、外边距以及边框会被应用且会把其他处于 inline 状态的盒子推开。

在这里最好也解释下内部 和 外部 显示类型。如上所述, css 的 box 模型有一个外部显示类型,来决定盒子是块级还是内联。

同样盒模型还有内部显示类型,它决定了盒子内部元素是如何布局的。默认情况下是按照 **正常文档流 **布局,也意味着它们和其他块元素以及内联元素一样 (如上所述).

但是,我们可以通过使用类似 flex 的 display 属性值来更改内部显示类型。如果设置 display: flex,在一个元素上,外部显示类型是 block,但是内部显示类型修改为 flex。该盒子的所有直接子元素都会成为 flex 元素,会根据弹性盒子(Flexbox)规则进行布局

什么是CSS盒子

完整的 CSS 盒模型应用于块级盒子,内联盒子只使用盒模型中定义的部分内容。模型定义了盒的每个部分 —— margin, border, padding, and content —— 合在一起就可以创建我们在页面上看到的内容。为了增加一些额外的复杂性,有一个标准的和替代(IE)的盒模型。

盒模型的各个部分
CSS 中组成一个块级盒子需要:

Content box: 这个区域是用来显示内容,大小可以通过设置 width 和 height.
Padding box: 包围在内容区域外部的空白区域; 大小通过 padding 相关属性设置。
Border box: 边框盒包裹内容和内边距。大小通过 border 相关属性设置。
Margin box: 这是最外面的区域,是盒子和其他元素之间的空白区域。大小通过 margin 相关属性设置。

标准盒模型

在标准模型中,如果你给盒设置 width 和 height,实际设置的是 content box。 padding 和 border 再加上设置的宽高一起决定整个盒子的大小。 见下图。

假设定义了 width, height, margin, border, and padding:

1
2
3
4
5
6
7
.box {
width: 350px;
height: 150px;
margin: 25px;
padding: 25px;
border: 5px solid black;
}

如果使用标准模型宽度 = 410px (350 + 25 + 25 + 5 + 5),高度 = 210px (150 + 25 + 25 + 5 + 5),padding 加 border 再加 content box。

替代盒模型

你可能会认为盒子的大小还要加上边框和内边距,这样很麻烦,而且你的想法是对的 ! 因为这个原因,css 还有一个替代盒模型。使用这个模型,所有宽度都是可见宽度,所以内容宽度是该宽度减去边框和填充部分。使用上面相同的样式得到 (width = 350px, height = 150px).

默认浏览器会使用标准模型。如果需要使用替代模型,您可以通过为其设置 box-sizing: border-box 来实现。 这样就可以告诉浏览器使用 border-box 来定义区域,从而设定您想要的大小。

如果你希望所有元素都使用替代模式,而且确实很常用,设置 box-sizing 在

元素上,然后设置所有元素继承该属性,正如下面的例子。如果想要深入理解,请看 the CSS Tricks article on box-sizing。

1
2
3
4
5
6
html {
box-sizing: border-box;
}
*, *::before, *::after {
box-sizing: inherit;
}

总结

简单来说,盒子模型指的是这个元素的宽和高是怎么计算的,而BFC指的是它内外部的元素不会有相互影响

是否是盒子模型是可以通过css属性直接设置的,比如display属性,但是是否是BFC并不是css的属性,而是有些情况下,会创建一个上下文。