一直都知道margin塌陷可以用overflow/float或增加border/padding解决,却一直不知道overflow/float为什么可以解决这个问题。今天彻查了一下,发现这是和BFC相关的。
格式化上下文
提到BFC,首先从格式化上下文开始。在常规流中的框,都属于一个格式化的上下文中。这个上下文可能是块格式上下文,可能是行内格式上下文,但不可能同时是块格式上下文和行内格式上下文。
块框参与块格式上下文,行内框参与行内格式上下文。(之前不懂这句话,不觉得块框只参与块格式上下文,行内框只参与行内格式上下文,直到理解了匿名框:可视化格式模型和各种框。我的理解就是,不符合规范的框都被符合规范的框圈了起来。)
BFC(块格式化上下文)
BFC(Block Formatting Context),直译为块级格式化范围。
是 W3C CSS 2.1 规范中的一个概念,它决定了元素如何对其内容进行定位,以及与其他元素的关系和相互作用。当涉及到可视化布局的时候,Block Formatting Context提供了一个环境,HTML元素在这个环境中按照一定规则进行布局。一个环境中的元素不会影响到其它环境中的布局。
简单说,BFC就是一个独立的容器,这个容器完全隔离,容器里box的布局和容器外互不相关,既不会被容器外的元素影响,也不会影响到容器外的元素。
如何触发BFC
元素满足以下任意一条件即可触发BFC:
- 根元素
- float的值不为none;
- postion的值不为static或relative;
- display的值为table-cell、table-caption、inline-block、flex或者inline-flex中的一个;(即不是块级框的块容器框,块容器框定义那边举例的是table-cell和inline-block)
- overflow的值不为visible。
BFC的特征
- 内部的Box会从包含块的顶部开始,在垂直方向一个接一个地放置;
- Box垂直方向的距离由margin决定,属于同一个BFC的相邻两个Box的margin会发生重叠;
- 每个元素的左外边界,与包含块的左边界相接触(对于从左往右的格式,否则相反),即使存在浮动也是如此,除非这个框建立了一个新的BFC;
- 关于这一点,详细说一下。“即使存在浮动也是如此”,指的是有一个浮动元素和一个static元素,浮动元素和static元素都会与包含块的左边界相接触(哪怕两个元素重叠)。而后面“除非这个框建立了一个新的BFC”,指的则是关于多个float或inline-block元素会表现得不一一与左边界接触,而是并排显示,以及后文“关于BFC不会与float box重叠”等现象。这些元素都建立了新的BFC。
相关问答,来自我在SegmentFault的提问关于BFC的特征,为什么说内部的box会在垂直方向一个接一个放置?,十分感谢Druidiurd大大的解答。
BFC的区域不会与float box重叠;
- BFC就是页面上的一个隔离的独立容器,容器里面的子元素不会影响到外面的元素。反之也如此;
- 计算BFC高度时,浮动元素也参与计算。
关于BFC不会与float box重叠
- 本身,如果元素不是一个BFC,那么周围的浮动元素可以覆盖(压住)这个元素。而如果这个元素是一个BFC,则浮动元素不会覆盖它,表现为它会移动(固定宽度)or收缩(宽度不定)避开浮动元素。
- 但是,只有浮动元素出现在BFC元素之前,才会出现这个表现。如果浮动元素出现在BFC元素之后,只会按部就班地浮动在元素后面而已。
1 | <div class="wrapper"> |
粉色元素浮动,橙色元素被覆盖:
给橙色元素添加overflow: hidden,使橙色元素成为一个BFC。橙色元素不与float box重叠,避让开来:
BFC应用场景
1. 解决margin折叠的问题 - 利用BFC内部外部完全隔离的特性
至此,我也总算真正明白为什么overflow/float可以解决margin叠加的问题了。首先,CSS中外边距折叠的条件是:
- 必须是处于常规文档流中(没有float和绝对定位)的块级盒子,并且处于同一个BFC中;
- 没有inline盒子(经测试,如果是display: inline的盒子,没有填充物的0*0display: inline盒子仍无法阻止margin折叠,有填充物则可以阻止;而对于其他行内级元素,如display: inline-block/inline-table/inline-flex/inline-grid等,不需要内容即可以成功阻止margin折叠),没有空隙,没有padding和border将它们分隔开来;
- 都属于垂直方向上相邻的外边距。
正因如此,才衍生出了使用padding/border将父子(或祖子)两个盒子分隔开来,或给父元素(或祖先元素)增加overflow非visible属性/float非none属性,使得父元素(或者祖先元素)成为一个独立的BFC,与子元素不处于同一个BFC,破坏margin折叠的条件。
2. 自适应两栏/三栏布局 - 利用特性,BFC不会与float box重叠
左右定宽中间自适应三列布局
本质上,就是先浮动两个元素,然后触发第三个元素BFC,BFC则会自动收缩,绕开前面的两个浮动元素
- 注意浮动元素需在BFC元素之前
- 两列布局同理
1 | <div class="container"> |
3. 清除浮动 - 利用特性,计算BFC高度时,浮动元素也参与BFC计算
众所周知,可以给浮动元素的父元素设置overflow非visible来清除浮动,不再赘述。
IFC(行内格式化上下文)
行框(line box)
相对于块格式化上下文,在行内格式化上下文中,框一个接一个水平排列,起点是包含块的顶部,水平方向上的margin、border和padding在框之间得以保留(垂直方向上的margin不保留)。框在垂直方向上可以以不同的方式对齐:顶部或底部对齐,或根据其中文字的基线对齐。包含这些框的长方形区域会形成一行,叫做行框(line box)。
行框的宽度由它的包含块和包含块中的浮动元素决定,高度由行高计算规则决定。
行框的范围
通常,行框的左边接触到其包含块的左边,右边接触其包含块的右边。然而,浮动元素可能会处于包含块的边缘和行框边缘之间,即将行框的宽度挤小。
行框通常高度不同(如一行包含了一个高的图形,其他行只包含了文本)。
行内框(inline box)可能被分隔
如果几个行内框在水平方向上无法放入一个行框中,他们可以分配在两个或多个垂直堆叠的行框中。因此,一个段落就是行框在垂直方向上的堆叠。行框在堆叠时没有垂直方向上的分割且永不垂叠。
如果一个行内框超出包含它的行框的宽度,它会被分割为几个框,并且这些框会被分步到几个行框内。如果一个行内框不能被分割(如只包含单个字符,或者语言特殊的断字规则不允许在行内框里换行,或行内框有“nowrap”或“pre”值的“white-space”特性影响),此时,行内框会溢出行框。
如果一个行内框被分割,margin、padding和border在分割处没有视觉效果。
空的行内框会被忽略
行框是为了支持IFC中的行内级内容的需要而被创造的。不包含文本,不包含white-space为pre的字符,不包含具有非零margin、padding或者border的行内元素,且不包含其他在常规流中的内容(比如图片、inline blocks或inline tables),且没有以一个pre的新行结尾的行框,在以测量任何在它们里面的元素的位置为目的时,一定要背当做零高度行框来对待;在以其他目的时,一定要被当做不存在。
参考文献
- 滴滴公共前端团队,邱莲,细说CSS中的BFC
- W3Help,武利剑,常规流
- W3C标准,Visual formatting model
- 我的提问,关于BFC的特征,为什么说内部的box会在垂直方向一个接一个放置?