沐光

记录在前端之路的点点滴滴

浅谈 BFC

前言

CSS 中除了垂直居中之外,另外一个常见的问题就是 BFC 问题。BFC 全称为 Block Fromatting Context,即“块级格式化上下文”,对于前端开发来说,了解这个还是非常必要的,因为对于 To C 类的产品对于前端 CSS 要求还是挺高的,了解这个能够更为方便的把握住页面的结构层次以及会产生的问题。

创建 BFC

下列方式会创建块格式化上下文(此部分由 MDN 总结):

  • 根元素或包含根元素的元素
  • 浮动元素(元素的 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、row、tbody、thead、tfoot 的默认属性或 inline-table)
    overflow 值不为 visible 的块元素
  • display 值为 flow-root 的元素
  • contain 值为 layout、content或 strict 的元素
  • 弹性元素(display 为 flex 或 inline-flex 元素的直接子元素)
  • 网格元素(display 为 grid 或 inline-grid 元素的直接子元素)
  • 多列容器(元素的 column-count 或 column-width 不为 auto,包括 column-count 为 1)
  • column-span 为 all 的元素始终会创建一个新的 BFC,即使该元素没有包裹在一个多列容器中(标准变更,Chrome bug)。

很多博客对于 BFC 这部分的知识有很详细的讲解了,但是好记性不如烂笔头对吧,我自己也试着研究了下,BFC 无外乎两个问题:高度塌陷和外边距重叠。

高度塌陷

这个现象在使用浮动布局时会时常遇到,用一下代码来说明:

1
2
3
<div class="container">
<p class="float-content">This is a float element</p>
</div>
1
2
3
4
5
6
7
8
9
.container {
background-color: bisque;
width: 100%;
}
.float-content {
float: left;
background-color: antiquewhite;
width: 30%;
}

由于子元素为浮动元素,脱离了文档流,因此父元素的内容为“空”,因此父元素的高度最终计算为 0。

高度塌陷

解决这个的方法也有很多,这里就不做全量总结了,最为推荐的方法为添加伪元素法:

1
2
3
4
5
6
7
.container::after {
content: "";
clear: both;
display: block;
height: 0;
visibility: hidden;
}

外边距折叠

外边距折叠问题用图不是很好描述,这里先简单的用字符画来描述一下(两种情形)。

第一种情形是,当你在一个 div 元素内插入一个块级子元素,然后设置其 margin-top 值,这是你会发现并不是子元素在父元素内撑开了一段距离,而是父元素向上撑开了一段距离。

1
2
3
4
5
6
7
8
9
10
11
12
原以为:                            实际上:
------- body ------- ------- body -------
*
--- parent --- | 50px
* *
| 50px --- parent ---
*
child child

--- parent --- --- parent ---

------- body ------- ------- body -------

另一种情况是,当你在 div 元素内插入多个块级元素,你给其中相邻的两个设置 margin-topmargin-bottom ,你会发现,他们之间的距离为相对应的 margin-topmargin-bottom 中的最大值,当设置第一个块级元素的 margin-top 属性则会像第一种情形那样,父级框移动了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
原以为:

-------- parent -----------
| 10px
childA
10px |
| ===> 30px
20px |
childB
| 20px
|
-------- parent ----------


实际上:
-------- body ---------
| 10px
---- parent ----
childA
|
| max(10px, 20px) ==> 20px
childB
---- parent ----
|
| 20px
-------- body ---------

外边距合并指的是,当两个垂直外边距相遇时,它们将形成一个外边距。合并后的外边距的高度等于两个发生合并的外边距的高度中的较大者。

这里有个比较通俗易懂的例子:

第一种情形

A 与 B 并排站,A 说:我站在距离前方 1m 的地方,接下来该你站位子了。
由于 A 与 B 之间没有明显的分割线,B 想了想没有动,说到:我现在和你在一条线上,那么我也应该站在距离前方 1m 的地方吧!

第二种情形

A 与 B 相对站,A 说:我站在距离你 1m 的地方,该你了。
B 说:我应该站在距离你 2m 的地方,通过相对原理,我们应该间隔 2m 吧~

从例子可以分析出,只要给予一个界限,即两者不是互相比对,基于某一个定点来决定位置,也就是我们说的形成 BFC 了

奇怪的布局

当在 div 元素内插入多个行内块级元素,你给其中任意一个或者多个设置 margin-top 想要使得它/它们表现得与众不同,可是,到头来所有元素都会移动,而且唯一准确的只有所设值最大的那个子元素,其他子元素则混淆。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
(注:所有子元素高度为 height: 100px)
原以为:
parent
----------------------------------------------------
| 50px childB | 25px
childA childC
----------------------------------------------------
parent

实际上:
parent
----------------------------------------------------
| 50px | 50px + 25px | 50px - 25px
childA childB childC
----------------------------------------------------
parent

代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>CSS外边距塌陷</title>
</head>

<style type="text/css">
.parent {
background-color: black;
height: 500px;
overflow: hidden;
width: 500px;
}
.parent div {
display: inline-block; /*包含了 wrapper */
}
.childA {
background-color: greenyellow;
height: 100px;
margin: 50px 0;
width: 100px;
}
.childB {
background-color: aliceblue;
height: 100px;
width: 100px;
}
.childC {
background-color: orangered;
height: 100px;
margin: 25px 0;
width: 100px;
}
/* 此为解决办法 */
/* .wrapper {
height: 100%;
overflow: hidden;
} */
</style>

<body>
<div class="parent">
<div class="wrapper">
<div class="childA"></div>
</div>
<div class="wrapper">
<div class="childB"></div>
</div>
<div class="wrapper">
<div class="childC"></div>
</div>
</div>
</body>
</html>

提示点:

    1. 默认对齐方式:基线对齐
    1. 空元素的基线位置:底部
    1. block 元素的 inline 内容默认位置:block 元素的顶部
    1. 盒子的默认类型: content-box

可基于此 4 点,结合代码看页面呈现效果,应该提示的很清晰了,脑补一下即可~

参考文档

附录

此篇同样为原写的文章的一次改版,更新原有的错误,记录新的体会!