写在前面:今天的内容看着简单------就三个文件,一个 readme 加两个 HTML。但讲的是 CSS 布局最底层的东西:BFC(Block Formatting Context,块级格式化上下文)。搞懂了 BFC,三列布局、清除浮动、margin 塌陷这些"玄学问题"全都能解释通。课堂还对比了 flex 和 grid 两种格式化上下文------一个是"单行高速公路",一个是"二维棋盘"。以下所有代码和概念均来自课堂真实文件。
一、三列布局:谁先加载谁是大爷
PC 端最常见的布局方案------三列布局:左右两列固定宽度,中间自适应。
readme 一句话点明了精髓:
"main 最先加载,中间先渲染,最快看到最有效的内容。左右两侧往往广告、导航,可以晚一点。"
这里藏着一个很多人忽略的细节------HTML 的书写顺序决定了加载顺序。
看课堂的 1.html:
html
<div class="layout">
<aside id="left" class="sidebar left">Left</aside>
<main class="content">Main Content</main>
<aside id="right" class="sidebar right">Right</aside>
</div>
注意:<aside id="left"> 写在 <main> 前面。但 readme 明确说了"main 最先加载"------这不是矛盾吗?
不矛盾。这里的关键在于 CSS 的 display: flex 可以重排视觉顺序 ,但 HTML 的 DOM 顺序决定了谁先被解析、谁先被渲染 。实际项目中,如果想真正做到 main 先加载,应该把 <main> 写在 DOM 最前面,再用 CSS order 属性调整视觉位置。
readme 给了三种实现方案:
"flex & grid & float"
三种工具,同一个目标。今天课堂用 flex 做了示范:
html
<style>
.layout {
display: flex; /* 弹性布局 */
}
</style>
一个 display: flex,三列布局的地基就搭好了。左 aside、中间 main、右 aside,自动排成一行。中间 main 设 flex: 1 自适应,左右固定宽度------经典三列布局完成。
二、BFC:浏览器的"独立领地"
三列布局是表象,BFC 才是底层。
什么是格式化上下文?
readme 的解释从根上讲起:
"html 开始,根,开启了第一个格式化上下文 BFC。块级元素从上到下,行内元素从左到右排列,最基本的文档流。"
页面一打开,<html> 根元素就开启了第一个 BFC------这就是整个页面的"根本大法"。在这个 BFC 里:
- 块级元素(div、p、section)从上到下排
- 行内元素(span、a、strong)从左到右排
这就是最基本的文档流(Normal Flow)。所有元素默认遵守这套规则。
为什么要开启新的 BFC?
问题来了------一个 BFC 的规则太单一了,只能从上到下排。我想让几个元素横向排列怎么办?
readme 的回答:
"一个块,加上多列?inline 不适合做盒子(容器)。局部布局方案,可以开启一个新的 BFC。"
开启新 BFC 的方式有好几种:
"block 元素 + display: flex | grid | table... float: left。position, overflow: hidden 开启一个新的 BFC。"
翻译一下------以下任一操作都会开启一个新的 BFC:
| 触发方式 | 代码 | 场景 |
|---|---|---|
| display: flex | display: flex |
弹性布局 |
| display: grid | display: grid |
网格布局 |
| display: table | display: table |
表格布局 |
| float | float: left |
浮动布局 |
| position | position: absolute / fixed |
脱离文档流 |
| overflow | overflow: hidden |
清除浮动 |
BFC 的"领地法则"
新 BFC 开启后,最重要的一条规则:
"新的格式化上下文,有自己的主意(格式化)。外层 BFC 格式化上下文不会影响到内部的新的格式化上下文。"
这就是 BFC 的核心------独立领地,内外隔离。
外面的规则管不到里面,里面的行为也影响不到外面。就像一块土地被划成了"经济特区"------特区外面实行一套法律,特区内部实行另一套法律,互不干涉。
这就是为什么 BFC 能解决那么多 CSS 玄学问题:
- margin 塌陷:两个相邻块元素的 margin 会合并,但开了 BFC 的容器不会跟子元素 margin 合并------领地隔离。
- 浮动溢出 :子元素 float 导致父元素高度塌陷,父元素开
overflow: hidden触发 BFC------领地包裹住了浮动。 - 文字环绕:float 元素会被文字环绕,但 BFC 区域不会与 float 元素重叠------各管各的领地。
三、Flex vs Grid:两种"领地治理模式"
课堂的 2.html 里有一段关键注释:
html
<!-- 3列布局,新的格式化上下文,原来的旧BFC 不好做
GFC 直接可以定义二维布局的方案
-->
这段注释点出了两个概念:BFC 和 GFC。
readme 也提到了:
"Block Formatting Context,Flex Formatting Context。"
实际上,CSS 里有多种格式化上下文:
| 缩写 | 全称 | 能力维度 |
|---|---|---|
| BFC | Block Formatting Context | 一维,从上到下 |
| FFC | Flex Formatting Context | 一维,主轴方向 |
| GFC | Grid Formatting Context | 二维,行 + 列 |
BFC:一维的"单行道"
默认的 BFC 只能从上到下排。想横向排列?得开新 BFC。
FFC(Flex):一维的"高速公路"
display: flex 开启 FFC------弹性格式化上下文。它在一根主轴上排列元素,可以横排也可以竖排,但本质还是一维的。
课堂的 1.html 用的就是 flex:
html
<div class="layout">
<aside id="left" class="sidebar left">Left</aside>
<main class="content">Main Content</main>
<aside id="right" class="sidebar right">Right</aside>
</div>
三个元素在一根主轴上排成一行------简单、直观、够用。大部分三列布局场景,flex 一把梭就够了。
GFC(Grid):二维的"棋盘"
display: grid 开启 GFC------网格格式化上下文。2.html 的注释说得很清楚:
"GFC 直接可以定义二维布局的方案。"
flex 是"一条线"排元素,grid 是"一张网"摆元素。行和列同时定义,元素可以放在任意格子------像下棋一样,横竖都能管。
三列布局用 flex 一行代码搞定,但如果你的布局复杂到"第一行三列、第二行两列、第三行一列跨满"------flex 就得嵌套好几层,grid 一个 grid-template-areas 搞定。
四、为什么 inline 不适合做盒子?
readme 有一句容易被忽略的话:
"一个块,加上多列?inline 不适合做盒子(容器)。"
为什么?
inline 元素是行内元素------<span>、<a>、<strong> 这些。它们的特性是不独占一行 ,而且不能设宽高 。你给 <span> 设 width: 200px,浏览器直接无视。
"盒子"需要什么?需要能设宽高、能容纳子元素、能开启自己的格式化上下文。inline 元素这些都做不到。
所以做布局容器,只能用块级元素 (div、section、main、aside),再通过 display: flex / grid 或 float 让它"变身"------从一个只会从上到下排的 BFC,变成一个能横向排列的新格式化上下文。
五、float:曾经的"布局之王"
readme 提到了 float 作为三列布局的方案之一。虽然今天 flex 和 grid 已经是主流,但 float 在历史上扛了十多年的布局大旗。
float: left 的本意不是做布局------它是让文字环绕图片用的。但 2000 年代的前端工程师发现:把一堆块级元素都 float: left,它们就能横向排列。于是 float 被硬生生用成了布局工具。
float 的代价是什么?父元素高度塌陷。 子元素全浮了,父元素"空了",高度变成 0。这就是 BFC 登场的地方------给父元素加 overflow: hidden 触发新 BFC,父元素就能包裹住浮动子元素。
readme 里那句"外层 BFC 不会影响内部新的格式化上下文",在 float 场景下就变成了------父元素的 BFC 包裹住了浮动子元素的浮动效果,不让它"溢出"到外面去。
现在有了 flex 和 grid,float 终于可以回归本职工作------文字环绕图片。但理解 float 和 BFC 的关系,仍然是理解 CSS 布局底层机制的钥匙。
六、overflow: hidden 的"隐藏技能"
readme 把 overflow: hidden 列为开启 BFC 的方式之一。很多人只知道它能"裁剪溢出内容",不知道它还能触发 BFC。
overflow: hidden 触发 BFC 后:
- 父元素能包裹浮动子元素(解决高度塌陷)
- 父元素不会与子元素的 margin 合并(解决 margin 塌陷)
- 父元素区域不会与外部浮动元素重叠(解决文字环绕)
副作用是------真的会把溢出的内容裁掉。如果你有 position: absolute 的子元素超出父元素范围,overflow: hidden 会把它切了。
所以现代开发中,触发 BFC 更推荐用 display: flow-root------专门为了触发 BFC 而生,没有副作用。但课堂的 readme 没提这个,说明课程聚焦的是 BFC 的核心原理,而非 API 细节。原理搞懂了,用哪个触发方式只是选工具的事。
七、一张图看懂格式化上下文
xml
<html> ← 根元素,开启第一个 BFC(根本大法)
│
├── 块级元素:从上到下排
├── 行内元素:从左到右排
│
└── <div class="layout" style="display: flex">
│
← 开启新的 FFC(Flex 格式化上下文,独立领地)
│ 外层 BFC 的规则管不到这里
│ 这里实行"主轴排列"的新法律
│
├── <aside> Left ← 主轴上排第一
├── <main> Content ← 主轴上排第二
└── <aside> Right ← 主轴上排第三
每一层格式化上下文都是一个"独立领地":
- 根 BFC 是最大的领地,管整个页面
display: flex的容器开了 FFC 领地,里面按主轴排列display: grid的容器开了 GFC 领地,里面按二维网格排列overflow: hidden的容器开了新 BFC 领地,里面包裹浮动
领地可以嵌套,但规则不互通。 这就是 CSS 布局的根本逻辑。
八、三列布局的"加载优先级"哲学
回到开头------readme 为什么强调"main 最先加载"?
因为用户打开页面,最想看到的是内容,不是左侧导航和右侧广告。
| 区域 | 内容 | 优先级 |
|---|---|---|
| main | 文章正文 / 产品列表 / 核心功能 | 最高,先加载 |
| left | 导航 / 菜单 | 中等,可以晚一点 |
| right | 广告 / 相关推荐 | 最低,最后加载 |
这不仅是技术问题,是用户体验设计。慢一秒看到广告无所谓,慢一秒看到正文------用户可能就关了。
三列布局的实现方式有三种(flex / grid / float),但"main 优先"的原则只有一条。工具可以换,原则不能丢。
PS:CSS 不是玄学,是"领地法"。搞懂 BFC,你就是浏览器的"规划局局长"。