说实话三栏布局我面试被问过不下三次,每次我都能背出来 "左右固定,中间自适应",再把 flex、grid、float 三种方案列一下,基本就过了。但前阵子我翻自己的笔记,发现写着 "BFC 很重要",可我盯着这三个字愣了半分钟------到底它重要在哪?跟三栏布局又是怎么勾上的?
我决定这次不复述答案,就顺着自己写页面的感觉一点点抠。
先别扯 BFC,三栏布局到底在解决什么
做 PC 端页面的同学都眼熟这个套路:左边导航 200px,右边广告 240px,中间的正文能随着屏幕宽度伸缩。看着简单对吧,我一开始也觉得不就是三个盒子并排嘛。
结果我最早写的时候,直接给三个 div 都加 display: inline-block,中间那个写 width: 100%,页面直接崩了。中间那一列把整行占满,左右两列被挤到下一行去。当时我没绕过来,后来才发现,这根本不是宽度的问题,是这三个盒子在同一个默认的排版规则里,而这个规则并不擅长处理 "两固定一弹性" 这种多列容器关系。
说白了,默认的文档流就像学校食堂的打饭队伍,块级元素一个接一个竖着排,行内元素挤在同一行横着走。但三栏布局要的不是 "挤在一行",是 "三个柱子各自占好地盘,中间那个多吃多占"。这就不是原来那条队伍的活儿了,得开个新窗口。
开新窗口这件事,就是 BFC
我一直以为 BFC 是一个具体属性,翻笔记才反应过来它其实是个环境。可以把它类比成公司里的独立项目组:公司大环境(html 根元素)有一套默认规则(正常文档流),如果某个部门拉了个新项目组(BFC),那这个项目组内部怎么排班、谁跟谁对齐,按自己的规矩来,外面的队列不往里掺和,里面的元素也不跑出去影响大环境的排版。
触发新 BFC 的方式有好几种,笔记上记了:display: flex、display: grid、display: table、float: left/right、overflow: hidden,还有绝对定位。说实话以前我只会背这串列表,捋到这我突然想通了,这些属性长得完全不一样,但它们干了同一件事------把自己从原来的文档流队伍里拎出来,单独开个组。
三栏布局里的 .layout 容器,本身还是个块级盒子,但只要给它加一句 display: flex,它内部就不是原来的 BFC 规则了,而是变成了 FFC(Flex Formatting Context,弹性格式化上下文)。名字吓人,其实就是弹性盒子这套专属的排版规则。grid 同理,叫 GFC。它们仨本质上是兄弟,都是 "开新上下文",只是内部玩法不一样。
跑了个最小 demo,效果比我想的还朴素
说再多不如看代码,我自己写了个最瘦的版本,删得不能再删了:
html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>三栏布局最小 demo</title>
<style>
* { box-sizing: border-box; margin: 0; padding: 0; }
.layout {
display: flex; /* 这里很容易搞反:写在父容器,不是子项 */
min-height: 100vh;
color: #fff;
text-align: center;
line-height: 100vh;
}
.left, .right {
width: 200px; /* 左右定宽 */
background: #3b82f6;
}
.right { background: #ef4444; }
.content {
flex: 1; /* 中间把剩下的空间全吃掉 */
background: #10b981;
}
</style>
</head>
<body>
<div class="layout">
<aside class="left">Left 导航</aside>
<main class="content">Main 主体内容</main>
<aside class="right">Right 广告</aside>
</div>
</body>
</html>
浏览器打开的瞬间,三块齐刷刷立在那,我拉窗口宽度,中间那一列跟着变,左右两根柱子纹丝不动。
这里有个面试爱问的小细节:为什么 HTML 里 main 写在中间?因为浏览器解析 DOM 是按顺序的,写在前面的先渲染。把 main 放最前面,用户第一眼看到的就是正文,左右两侧的导航和广告晚几十毫秒没关系------这就是笔记里那句 "main 最先加载" 的意思。我之前一直以为是为了语义化,原来还有性能层面的小心思。
想让 main 写在最前面又保持视觉顺序,flex 用
order就可以,grid 用grid-template-areas更直观,本质都是把 "源码顺序" 和 "视觉顺序" 解耦。
翻完规范我愣了,原来 float 才是老前辈
flex 和 grid 写起来太顺了,我差点忘了 float 方案。老式的三栏布局,左右两个 aside 用 float: left 和 float: right,中间 main 写在最后但放中间。为什么它能行?因为 float 本身也会触发 BFC,左飘和右飘的元素相当于从正常流里 "飞" 出来,贴在两边,中间的盒子自然就填在剩下的空间里。
但是 float 有个祖传毛病:如果父容器没清浮动,高度会塌成 0,俗称高度塌陷。本质上就是父容器自己还在默认的 BFC 里,它看不见内部那些 float 出来的孩子。解决方式也很朴素------让父容器也开一个 BFC(比如 overflow: hidden),这样父子俩在同一个上下文里,爹就能认出孩子了。
说到这我才反应过来,以前面试被问 "怎么清浮动",我背了三四种方案,其实核心原理就一句话:让父盒子进入一个能看到浮动元素的格式化上下文。背方案不如背原理,原理记住了,方案随便写。
踩过的小坑,都是当时脑子没转过来
说实话我写这个 demo 的时候,还真有两个差点掉进去的地方。一个是把 display: flex 写到了子元素上,结果三列完全没反应,愣了半天才发现是写反了位置------FFC 是父容器开启的,孩子只是参与规则。
另一个坑是给中间列写了 width: 100%,结果左右直接被挤没。中间要的是 flex: 1,意思是 "把剩余空间按比例分给我",不是 "我要占满整行"。这俩长得像,心态上完全不同,一个是懂事的孩子吃剩菜,一个是熊孩子抢整桌。
还有个反直觉的点:display: block 的元素本身并不会开新的 BFC。我以前以为块级盒子天然就是 BFC,其实不是,html 根元素那个才是默认开的,其它 block 盒子只是在里面按规则排而已。想单独开新的,必须加前面说的那几个触发条件。
捋完的几点小收获,也说清楚它的边界
这次翻完最大的感觉是,我终于不再把 BFC 当成一个孤立的考点了。它其实是 CSS 排版体系里的 "隔离舱" 概念:默认的文档流是全局的一间大通铺,BFC、FFC、GFC 就是一个个小包间,包间里有包间的规矩,内外互不干扰。
所以三栏布局用哪种方案?我个人的体感是:日常开发直接 flex,简单不翻车;要做更复杂的二维排列,比如中间再分几行几列,上 grid;float 方案除非是维护老项目,否则别主动碰,高度塌陷和清除浮动的心智负担确实大。
适用边界也得说清楚:三栏布局本身是 PC 端思维,移动端窄屏基本没用。做响应式的时候一般配个媒体查询,窄屏下把三列直接折成单列,别硬塞。
顺手捋的核心知识点
写到这顺手把核心的点捋了一遍,我自己回头复盘方便,你们要是懒得翻长文也可以直接看这部分。
-
三栏布局目标
- 核心结论:左右两列固定宽度,中间列自适应剩余空间,是 PC 端典型的容器级布局方案。
- 易错提醒:别给中间列写
width: 100%,那会把整行占满,左右直接被挤到下一行。
-
BFC 到底是什么
- 核心结论:BFC(Block Formatting Context)是一个独立的排版环境,内部元素的布局不会影响外部,外部的规则也不会渗透进来。
- 易错提醒:不是所有
display: block元素都是 BFC,html 根元素才默认开启;普通 block 盒子只是按 BFC 规则排列。
-
BFC 的触发方式
- 核心结论:
display: flex/grid/table、float非 none、overflow非 visible、绝对/固定定位,这些属性都会为当前元素开启新的格式化上下文。 - 易错提醒:FFC(Flex)和 GFC(Grid)本质上也是一种独立上下文,只是内部排版规则不同,别把 FFC 和 BFC 当成互斥的两个东西。
- 核心结论:
-
flex 实现三栏的关键写法
- 核心结论:父容器
display: flex开启 FFC,左右子项定宽,中间子项用flex: 1吃掉剩余空间。 - 易错提醒:
display: flex写在父容器,不是写在三个子项上,新手很容易搞反位置。
- 核心结论:父容器
-
高度塌陷的本质与清除
- 核心结论:float 方案中父元素高度塌陷,是因为父容器在默认上下文里看不到脱离文档流的浮动子项;让父容器也开启 BFC 即可解决。
- 易错提醒:
overflow: hidden能清浮动不是什么 "黑魔法",底层原因就是它触发了新的 BFC。
-
main 优先加载的设计意图
- 核心结论:HTML 源码顺序中把
<main>放最前,配合order或 grid 区域调整视觉顺序,能让正文先渲染,提升首屏有效内容到达速度。 - 易错提醒:语义化只是附带收益,核心目标是 "最有效的内容让用户最先看到",不是为了过 HTML 校验。
- 核心结论:HTML 源码顺序中把
-
手动实现 float 版三栏的关键步骤
- 关键步骤:① 左 aside 写
float: left并定宽;② 右 aside 写float: right并定宽;③ main 放在两个 aside 之后并利用常规流填充中间;④ 父容器通过overflow: hidden或::after { clear: both }清除浮动,防止高度塌陷。 - 易错提醒:float 方案里 right 那个 aside 必须写在 main 前面,不然它会飘到 main 的下一行,这是 float 按源码顺序 "占坑" 的特性决定的。
- 关键步骤:① 左 aside 写
我目前理解到的程度大概就是这些,有补充的或者我哪里说漏了的,可以评论区唠唠。