一、概述
1. 什么是格式化上下文
官方的定义如下:
格式化上下文是 W3C CSS2.1 规范中的一个概念
它是页面中的一块渲染区域,并且有一套渲染规则,它决定了其子元素将如何定位,以及和其他元素的关系和相互作用
CSS2.1 中只有 BFC 和 IFC,CSS3 增加了 GFC 和 FFC
IFC:Inline Formatting Context(内联格式化上下文)
BFC:Block Formatting Context(块级格式化上下文)
GFC:Grids Formatting Context(网格格式化上下文)
FFC:Flexible Formatting Context(弹性盒格式化上下文)
通俗的理解:我们知道 HTML 页面是由一个一个盒子组成的,那这些盒子该怎么摆放在页面上面呢?这其实就是格式化上下文的工作;
只要写过一些 web 页面就知道:块元素“自带换行符”、行内(不可替换)元素无法设置上下外边距和高度;
这些我们平时觉得理所应当的规则其实就是格式化上下文的组成部分,浏览器就是按照这些规则来将我们写的页面展示给用户看的;
外面的元素形成了格式化上下文,它所包含的元素就要遵从格式化上下文规则;
2. 盒子的分类
既然页面是由一个个盒子组成的,那么都有什么盒子呢?
不同类型的盒子遵守不同的规则,像 <span> 默认是 inline 元素,就遵守 IFC 规则(行级格式化上下文),像 <div> 默认是 block 元素,就遵守 BFC 规则;
CSS3 新出了 flex 弹性布局和 gird 网格布局都十分好用,由于它们的布局规则和以往的 IFC 和 BFC 都不同,所以才新增了 FFC 和 GFC;
那要问那个规则“最大”呢?当然是 BFC 最大,因为网页的根元素就会形成 BFC 上下文,这是用来“兜底”的,我们一会儿会讲到;
接下来,我会对 BFC 和 IFC 进行一些分析;
二、块级格式化上下文(Block Formatting Context)
1. 布局规则
- 内部的盒子在垂直方向一个个放置
- 盒子垂直方向的距离由 margin 决定,相邻盒子的垂直外边距会发生折叠
- 每个元素的 margin box 的左边与包含块的 border box 的左边相接触(在从左到右排列的情况下,反之同理),浮动元素也一样
- BFC 的区域不会与浮动的盒子重叠
- 浮动元素也参与计算 BFC 的高度
- BFC 是页面上的一个隔离的独立容器,不会影响到外部,同样,外部也不会影响到 BFC 内部
为什么这样规定这些规则呢?
有人会这样回答:“人家这就是这样定义的,你就必须这样用!总要有一套规则的,记住就是了。”
这么答是没错的,不过我们也可以去尝试理解为什么作者这样制定规则,在试着理解之前,先说下什么情况下会形成 BFC
2. 形成条件(以下元素会生成 BFC,其内部的元素遵守 BFC 规则)
- 根元素(
<html>)
- 浮动元素(元素的
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
或 paint 的元素- 弹性元素(
display
为flex
或inline-flex
元素的直接子元素)- 网格元素(
display
为grid
或inline-grid
元素的直接子元素)- 多列容器(元素的
column-count
或column-width
不为auto,包括
column-count
为1
)column-span
为all
的元素始终会创建一个新的BFC,即使该元素没有包裹在一个多列容器中(标准变更,Chrome bug)
3. 理解
结合 BFC 的布局规则和形成条件,我们可以尝试着理解为什么作者制定这样的规则:
最简单的思考方式:如果没有某条规则或某种形成条件,会有什么不同?
为什么根元素会形成块级上下文而不是行级上下文?
这很好理解,页面是“一块”还是“一行”呢?明显是“一块”呀!
浮动元素会向左或向右浮动,而根元素又形成了 BFC,所以根据 规则3 和 规则5,浮动元素不会“跑出”根元素,且计算页面高度的时候会计算其中浮动元素的高度;
为什么绝对定位元素会形成 BFC?
我们可以想象如果绝对定位元素不形成 BFC 会怎样?我们就不能在绝对定位元素的子元素中应用浮动了,因为它一定会“跑”出去,而且高度也不会被计算到那个绝对定位元素中;正因为绝对定位元素形成了 BFC,所以我们才可以在里面应用浮动;
再者,根据 规则6:BFC 是页面上的一个隔离的独立容器,绝对定位元素都脱离文档流了,难道还算不上“一个隔离的独立容器”吗?
形成条件9 中的 flow-root 是什么属性?
答案:设置了 "display: flow-root;" 的元素在性质上等同于网页的根元素,是一个“自动形成 BFC”的 block 元素;
我们经常运用块级格式化上下文的 规则3 和 规则5 来清除浮动,希望用某个块元素包住其中的浮动元素,常见的作法如下:
运用规则前的代码:
/* CSS */ .container { display: block; width: 300px; min-height: 20px; background-color: chartreuse; } .float-box { float: left; width: 100px; height: 100px; background-color: coral; } <!-- HTML --> <div class="container"> <div class="float-box">Float Box</div> </div>
此时的效果(float-box 跑了出去;如果不是设置了最小高度的话,container 都没了):
我们在 container 的样式中加一条:
overflow: hidden;
根据 形成条件8,overflow 不为 visible 了,所以 container 形成了一个 BFC,其中的 float-box 就被 container “包裹住”了(参与计算高度,把 container 撑了起来):
这样操作有一个副作用,就是 overflow 被设置成其它值了;如果不希望做修改的话,现在可以直接将 container 的 display 设为 flow-root,就完成了;
注意 形成条件11 和 形成条件12,里面说的是:display 为 flex / inline-flex / grid / inline-grid 的元素的直接子元素 形成 BFC;
因为 flex 和 grid 元素的子元素之间还要按照 FFC 和 GFC 规则进行排布;至于 FFC 和 GFC 是什么,不就是 弹性布局 和 网格布局 的那两套规则吗;
形成条件10 中的 contain 属性是什么?
参考:https://developer.mozilla.org/zh-CN/docs/Web/CSS/contain
CSS
contain
属性允许开发者声明当前元素和它的内容尽可能的独立于 DOM 树的其他部分;这使得浏览器在重新计算布局、样式、绘图、大小或这四项的组合时,只影响到有限的 DOM 区域,而不是整个页面,可以有效改善性能;既然都独立了,那肯定要自己形成一个 BFC 呀?不然自己里面的浮动元素都跑到外面去了还算什么独立呀;
浮动元素为什么要形成 BFC?
理由和 “绝对定位元素为什么要形成 BFC” 一样;
为什么在 BFC 内,相邻盒子的垂直外边距会折叠?怎么折叠的?
怎么折叠:上边一个块盒子的下外边距是 200px,下边一个块盒子的上外边距是 300px,所以它们的距离在不折叠的情况下是 500px,折叠的情况下就取大的,实际距离是 300px;如果一个盒子没有内容、内边距和边框,它的上下外边距也会发生折叠,只留个大的;嵌套的盒子中,外边距碰到也要折叠,折叠之后如果还碰到新的,那么还要折叠;总之:垂直外边距,只要碰到就折叠;
为什么要折叠:段落排版时,我们给段落设置相同的上下外间距,因为会折叠,所以第一段前的空白就和第一二段之间的空白一样大了;可能作者认为这样比较方便;什么?你不这样认为?那可能还有别的原因吧;
可不可以避免折叠:可以,方法如下:在一个 BFC 之内的盒子才会折叠,所以让它们不在一个 BFC 中就好了(让下面或上面的盒子形成 BFC);
题外话:关于 “垂直外边距” 和 “清除浮动” 的一个坑:
我们可以通过 "clear: both;" 来清除浮动,它的实现原理是通过隐式地给这个元素添加一个足够大的上外边距,来 “躲开” 浮动元素;所以如果你给它手动设置了一个比较小的上外边距是不起作用的(“比较小” 指的是小于自动隐式加的外边距);
如何阻止元素被浮动元素覆盖?
根据 规则4:BFC 的区域不会与浮动的盒子重叠,让元素自己形成一个 BFC,就不会被浮动元素覆盖了;
可以通过这条规则做一个两栏布局:side栏:固定宽度,向左浮动;main栏:让自己形成一个 BFC,吞掉剩下的空间;
题外话:table-cell(单元格)属于 inline 还是 inline-block 还是 block?
答案:table-cell 作为一个 display 的值,和 inline、inline-block、block 是平级的,正是因为它和它们都不一样所以才单独拿出来作一个值,table-cell 具有它们都不具备的特性;table-cell 就是 table-cell,它不属于谁;(这看上去是废话,但我确实纠结了半天)
三、行级格式化上下文(Inline Formatting Context)
1. 形成条件
块级元素中仅包含内联级别元素时,这个块级元素形成一个 IFC,其中的内联元素遵守 IFC 规则;当 IFC 中有块级元素插入时,会产生两个匿名块将父元素分隔开,产生两个 IFC;(这是个递归定义,但是很简单)
也就是说:IFC 中只包含内联级别元素,它们是连续的,且尽可能地包含更多元素;(inline-block、inline-table、inline-flex、inline-grid 也算内联元素,参与 IFC 布局;它们内部的元素也参与 BFC、FFC 或 GFC 布局,当然它们内部的内联元素参与 IFC 布局)
2. 布局规则
在行内格式化上下文中:
- 元素一个接一个水平排列,起点是包含块的顶部(IFC 中的元素都是内联元素,作为 inline-level box,也被称为行内盒)
- 水平方向上的 margin,border 和 padding 在元素之间得到保留(垂直方向上则不计算空间)
- 元素在垂直方向上可以以不同的方式对齐:它们的顶部或底部对齐,或根据其中文字的基线对齐(由 vertical-align 属性控制)
- 包含那些元素的长方形区域,会形成一行,叫做行盒(line box)
关于行盒 (line box):
- 包含来自同一行的盒的矩形区域叫做行盒 (line box)
- line box 的宽度由包含块和 float 情况决定,一般来说, line box 的宽度等于包含块两边之间的宽度,然而 float 可以插入到包含块和行盒边之间,如果有 float,那么 line box 的宽度会比没有 float 时小
- line box 的高度由 line-height 决定,而 line box 之间的高度各不相同 (比如只含文本的 line box 高度与包含图片的 line box 高度之间)
- line box 的高度能够容纳它包含的所有盒,当盒的高度小于行盒的高度 (例如,如果盒是 baseline 对齐) 时,盒的竖直对齐方式由 vertical-align 属性决定
- 当一行的行内级盒的总宽度小于它们所在的 line box 的宽度时,它们在行盒里的水平分布由 text-align 属性决定。如果该属性值为 justify,用户代理可能会拉伸行内盒(不包括 inline-table 和 inline-block 盒)里的空白和字(间距)
关于行内盒 (inline box):
- 一个 inline box 是一个(特殊的)行内级盒,其内容参与了它的包含行内格式化上下文
- 当一个 inline box 超出一个 line box 的宽度时,它会被分成几个盒,并且这些盒会跨多 line box 分布。如果一个 inline box 无法分割(例如,如果该 inline box 含有一个单个字符,或者特定语言的单词分隔规则不允许在该 inline box 里分隔,或如果该 inline box 受到了一个值为 nowrap 或 pre 的 white-space 的影响),那么该 inline box 会从 line box 溢出
- 当一个 inline box 被分割后,margin、border 和 padding 在发生分割的地方(或者在任何分割处,如果有多处的话)不会有可视化效果
- 同一个 line box 里的 inline box 也可能因为双向(bidirectional)文本处理而被分割成几个盒