URL
type
status
date
slug
summary
tags
category
icon
password
一、CSS中的格式化上下文
最近在看一本关于css的掘金小册,里面提到了格式化上下文这个概念。但是文章没有举太多例子,语言也比较概念化,因此我理解的迷迷糊糊。 最后查阅了MDN文档和大量的文章,终于对格式化上下文有了个基本的概念。
1. 什么是格式化上下文?
我知道页面上的元素默认是按流式布局排列的,即标准文档流或mdn翻译的常规流(normal flow)。这些元素也叫盒子(box),盒子有两种,块级盒子和内联盒子。这两种盒子在常规流中有自己的排列规则:比如块级盒子大概就是占一整行,从上往下依次堆叠;内联盒子能水平进行排列。这些都是我们熟知的排列方式。
那什么是格式化上下文呢?
实际上当我们在描述块级盒子该怎么排,内联盒子该怎么排时,我们说的就是块格式化上下文(BFC)和行格式化上下文(IFC)。所谓上下文,就是多个盒子在一起时,彼此之间的排列方式和影响。
但是这里面有许多的细节,这些细节深刻的影响着我们的排版,比如规范中规定:
“在一个块格式区域中,盒子会从包含块的顶部开始,按序垂直排列。同级盒子间的垂直距离会由“margin”属性决定。相邻两个块级盒子之间的垂直间距会遵循外边距折叠原则被折叠。 在一个块格式区域中,每个盒子的左外边缘会与包含块左边缘重合(如果是从右到左的排版顺序,则盒子的右外边缘与包含块右边缘重合)。” - 9.4.1
这就是垂直margin塌陷和合并的由来,块格式化上下文就是这么规定的!块盒子之间排列时就是这么个规则。
2. 类比js作用域来理解块格式化上下文BFC
格式化上下文中,块格式化上下文BFC是重中之重。因为它产生了许多副作用,比如margin塌陷和合并、浮动时的高度塌陷等等,对实际开发影响巨大。
也就是说,在一个块格式化上下文中,盒子和盒子之间是可以相互影响的!比如A盒子30px的margin,吞掉了下面B盒子20px的margin。这种相互影响不仅存在于兄弟之间,还存在于父子之间:比如C盒子内部的D盒子,也会在垂直方向上和父亲的margin融合。而符合我们直觉的方式,应该是兄弟的margin相加,儿子的margin应该把自己推离父亲的内壁。
既然格式化上下文是一套规则,那这套规则应该有个适用的范围吧。实际上,我们创建的页面中,html元素内部默认就会形成一个BFC。我们写在页面中的块元素一来就符合BFC的规则,就是因为这个最初的BFC,也正是因为这个最初的BFC,我们盒子中的margin才会相互缠绵和交融。
BFC的作用范围,实际上就是一个盒子内部,在这个盒子内部的块元素,都会按照BFC的规则进行排列。
在最初了解BFC时我有个误解,觉得每一个块级盒子内部都会生成一个BFC,这是大错特错的!要让一个盒子生成BFC,需要加入一些指定的css样式来触发。具体可以看MDN:区块格式化上下文。也就是说,如果不去触发某个盒子的BFC,那么最初html中的所有的盒子,不管兄弟还是父子孙子,都是在同一个BFC里面的。
这在形式上类似js中的作用域,原生js没有块级作用域,只有函数作用域。因此我们在全局作用域中不管打多少大括号,都不会生成新的作用域,除非我们刻意创建一个函数。BFC就是块盒子排列规则的作用域,只有当我们触发了BFC生成的条件,才能生成新的BFC。
3. 格式化上下文的作用:隔离
说了这么多,BFC有什么用呢?上面说到,同一个BFC里面,盒子之间会互相的影响,一个盒子能影响旁边的盒子,能影响祖先盒子。这使得布局出现一些反直觉和不好控制的地方。这就得用到BFC的隔离作用。
现在我们知道如何在一个盒子内生产新的BFC了,下面简称这种内部产生新BFC的盒子为BFC盒子。BFC盒子内部的盒子,属于这个新的BFC,而它和它的兄弟、祖先盒子属于外部的BFC。属于不同BFC的元素,无论怎么折腾,都无法影响另一个BFC的元素。因此,BFC盒子的子盒子的margin不再和其它BFC的盒子叠加了,也不会和父盒子的margin融合了,这样就实现了上面说的符合直觉的效果:
兄弟的margin相加,儿子的margin应该把自己推离父亲的内壁。
这里有个误区:BFC作用于一个盒子内部,而形成BFC的那个盒子本身,并不属于这个新的BFC,因此这个盒子还是会受外部BFC中盒子的影响。BFC仅仅隔绝了它的子盒子。想要做到兄弟盒子的margin互不影响,就得在其中一个盒子的外面套一个盒子专门用于生成BFC,比如要C盒子和E盒子margin不影响,就得在E盒子外面包裹一层outer盒子专门生成BFC,E盒子作为outer盒子的子盒子,自然不能影响另一个BFC中的C盒子。
同一BFC中,除了margin互相影响,浮动也会影响其它盒子,这主要是由于浮动脱离了普通流和排挤行内元素造成的。比如父盒子没有设置高度,子盒子浮动后会导致高度塌陷。或者有其它子盒子,浮动盒子会盖在其它盒子上面,并挤开内部元素。而我们往往用浮动,只是想用于定位,这些副作用都是多余的。
若不想高度塌陷,可以让父盒子生成BFC,浮动元素作为子盒子,不可以影响外部BFC的盒子,因此只能撑开父盒子,将父盒子作为护城河。
若不想让浮动元素遮盖兄弟盒子,则可以让兄弟盒子生成BFC,因为遮盖时,浮动盒子会影响兄弟盒子内部的元素,兄弟盒子形成BFC后,浮动盒子就不能影响兄弟盒子的内部了,只能把兄弟们推开。
(上面两种是自己想的一种理解方式,不一定对,但感觉能帮助理解)
至此,实际上就是MDN文档中给出的BFC的三大作用:
- 包含内部浮动。
- 排除外部浮动。
- 阻止外边距重叠。
4. 总结
简言之,在普通流中块级元素会相互影响,我们可以通过设置不同的BFC来隔绝这种影响。而产生影响的主要元凶就是浮动和外边距。因此我们知道如何利用BFC的特点来处理外边距重叠、浮动闭合等问题就行了。
BFC这个概念本身,我觉得是比较抽象的,因为浏览器根据规范去实现自己的机制,BFC只是我们去理解这套机制或现象的一个工具,知道有这么个东西、怎么触发和使用就好了。然后再搞清楚两个容易误会的点:
- BFC不是盒子本身,也不是每个盒子都有,而是某些盒子满足特定条件才能生成。
- BFC的作用范围在一个盒子内部,因此生成BFC的盒子本身也是会被原BFC中的元素影响的。
学习资源:
1. 浏览器层合成与页面渲染优化
这篇文章描述了浏览器层合成的过程,并基于层合成,给出了一些优化方案