【每日一面】盒模型与 BFC
盒模型由 content、padding、border、margin 四层组成,box-sizing 决定宽度算到哪;BFC 是封闭的渲染区域,管住 margin 折叠、清浮动和自适应两栏布局。
基础问答
Q1:CSS 盒模型是什么?有哪两种?区别在哪?
CSS 盒模型是浏览器渲染每个元素时的矩形盒子,由内到外分为四部分:content(内容)→ padding(内边距)→ border(边框)→ margin(外边距)。
两种盒模型:
| 模型 | box-sizing 值 | 宽度计算方式 |
|---|---|---|
| 标准盒模型(W3C 默认) | content-box | width = content 宽度,padding/border 会额外撑大盒子 |
| IE 盒模型(怪异模式) | border-box | width = content + padding + border,padding/border 包含在设定宽度内 |
/* 标准盒模型:实际占位 120px */
.box1 { box-sizing: content-box; width: 100px; padding: 10px; }
/* IE 盒模型:实际占位仍是 100px,内容被压缩为 80px */
.box2 { box-sizing: border-box; width: 100px; padding: 10px; }实际项目通常全局设置 * { box-sizing: border-box; },让布局更可预测。
Q2:margin 什么时候会发生合并(外边距折叠)?
外边距合并(Margin Collapsing)只发生在块级元素的垂直方向(上下 margin),常见三种场景:
-
相邻兄弟元素:上一个的
margin-bottom与下一个的margin-top合并,取较大者 -
父子元素:子元素的
margin-top穿透到父元素外面(父元素无边框、无 padding、无 BFC 隔离时) -
空块元素:自身上下 margin 合并,高度可能不生效
/* 相邻兄弟:实际间距是 30px,不是 50px */
.a { margin-bottom: 20px; }
.b { margin-top: 30px; }注意:水平方向的 margin 永远不会合并;浮动、绝对定位、flex/grid 容器内的元素也不会发生垂直合并。
Q3:什么是 BFC?如何触发?
BFC(Block Formatting Context,块级格式化上下文)是一个独立的渲染区域,内部元素的布局不会影响外部,外部也不会影响内部。
常见触发方式(满足任一即可):
-
根元素
<html> -
float不为none -
position为absolute/fixed -
display为inline-block、table-cell、table-caption、flex、grid、flow-root -
overflow不为visible(hidden/auto/scroll)
BFC 的三大用途:阻止外边距合并、清除浮动、实现自适应两栏布局。
Q4:清除浮动有哪些方式?
| 方式 | 原理 | 缺点 |
|---|---|---|
父元素加 overflow: hidden | 触发 BFC,包含浮动子元素 | 内容溢出会被裁剪 |
父元素加 display: flow-root | 现代专门触发 BFC 的值 | 旧浏览器不支持 |
| clearfix 伪元素 | ::after 生成块级盒子 clear: both | 需要额外 CSS |
结尾加空标签 clear: both | 利用清除属性撑开父元素 | 污染 HTML 结构 |
最推荐的现代写法是 display: flow-root,语义最准确、无副作用,但需要注意兼容性。
扩展延伸
格式化上下文与包含块
CSS 布局的核心是格式化上下文(Formatting Context)。页面上的每个元素都生活在某种格式化上下文中:块级盒子默认参与 BFC,行内盒子参与 IFC(行内格式化上下文)。BFC 之所以神奇,是因为它定义了一组封闭规则:
graph TD
subgraph 页面
subgraph BFC容器["BFC 容器(如 overflow:hidden)"]
F1["浮动元素 float:left"]
F2["普通块级元素"]
end
OUT["外部兄弟元素"]
end
F1 -->|"浮动被约束在 BFC 内"| BFC容器
BFC容器 -->|"高度自动包含浮动子元素"| OUT
OUT -.->|"不受内部浮动影响"| BFC容器
BFC 的核心规则有三条:
-
内部盒子垂直方向一个接一个排列,且 margin 会折叠
-
BFC 区域不会与浮动元素重叠
-
计算 BFC 高度时,浮动子元素也参与计算(这就是能清浮动的原因)
BFC 的妙用
graph LR
subgraph 问题
P1["① 父子 margin 穿透"]
P2["② 浮动塌陷"]
P3["③ 文字环绕浮动图"]
end
subgraph 触发BFC["触发 BFC"]
T["overflow:hidden<br>display:flow-root<br>float/inline-block 等"]
end
subgraph 效果
E1["父成 BFC<br>margin 被关在内部"]
E2["高度计算<br>包含浮动子元素"]
E3["文本容器成 BFC<br>不与浮动重叠"]
end
P1 --> T --> E1
P2 --> T --> E2
P3 --> T --> E3
-
解决 margin 穿透:给父元素触发 BFC,子元素的 margin 就被限制在 BFC 内部:
css.parent { overflow: hidden; /* 触发 BFC,子元素 margin-top 不再穿透 */ } -
清除浮动(高度塌陷):浮动元素脱离文档流,父元素高度变为 0。父元素触发 BFC 后,计算高度时会把浮动子元素算进去:
css.container { display: flow-root; /* 最干净的清浮动方式 */ } -
两栏布局:左栏浮动,右栏触发 BFC,右栏不再与浮动重叠,实现自适应:
css.left { float: left; width: 200px; } .right { overflow: hidden; /* BFC,自动避开左栏宽度 */ }
BFC 实战
两栏布局加清除浮动
<style>
.container {
display: flow-root; /* 清浮动:BFC 高度包含浮动子元素 */
border: 1px solid #ccc;
}
.sidebar {
float: left;
width: 200px;
background: #f0f0f0;
}
.main {
overflow: hidden; /* 触发 BFC:不与浮动重叠,自动占满剩余宽度 */
background: #e6f7ff;
}
</style>
<div class="container">
<div class="sidebar">侧边栏(浮动)</div>
<div class="main">主内容区:触发 BFC 后不会被浮动元素遮挡,宽度自适应剩余空间。</div>
</div>常见清浮动方案
| 方案 | 代码 | 原理 | 兼容性 | 推荐度 |
|---|---|---|---|---|
| flow-root | display: flow-root | 显式创建 BFC | IE 不支持 | ⭐⭐⭐⭐⭐ 现代项目首选 |
| overflow | overflow: hidden | 触发 BFC | 全兼容 | ⭐⭐⭐⭐ 注意溢出裁剪 |
| clearfix | .clearfix::after{content:"";display:block;clear:both} | 伪元素撑开 | 全兼容 | ⭐⭐⭐ 老牌经典 |
| flex/grid | 容器改 flex/grid | 子元素不再浮动 | 现代浏览器 | ⭐⭐⭐⭐ 顺手方案 |
面试追问
Q1:下面代码中,两个盒子的实际渲染宽度分别是多少?
<style>
.a { width: 100px; padding: 10px; border: 5px solid; box-sizing: content-box; }
.b { width: 100px; padding: 10px; border: 5px solid; box-sizing: border-box; }
</style>
<div class="a">A</div>
<div class="b">B</div>答:
-
A(content-box):实际宽度 =
100 + 10×2 + 5×2 = 130px,内容区保持 100px -
B(border-box):实际宽度 = 100px,其中内容区被压缩为
100 - 20 - 10 = 70px
border-box 的 width 包含到 border 为止,padding/border 越大,留给内容的越少。
Q2:下面代码的间距是多少?如何让间距变成 60px?
<style>
.top { margin-bottom: 30px; }
.bottom { margin-top: 30px; }
</style>
<div class="top">上</div>
<div class="bottom">下</div>答:实际间距是 30px(相邻兄弟垂直 margin 合并,取较大者)。
让间距变成 60px 的常见方法:
-
把其中一个元素放进 BFC 容器中(如
display: flow-root的 wrapper),切断合并关系 -
只保留一个方向的 margin(如只用
margin-bottom: 60px) -
改用 padding 实现间距
/* 方案一:BFC 隔离 */
.wrapper { display: flow-root; }
/* HTML: <div class="wrapper"><div class="top">上</div></div> */Q3:overflow: hidden 清除浮动有什么副作用?什么场景下不能用?
答:overflow: hidden 触发 BFC 清浮动的副作用有两个
-
内容裁剪:子元素超出容器部分(如绝对定位的下拉菜单、tooltip、负 margin 布局)会被直接裁掉
-
层级/滚动语义变化:
overflow: hidden会使元素成为新的包含块和裁剪上下文,可能改变position: absolute子元素的定位基准,也会阻止内部滚动
不能用的典型场景主要是容器内有下拉菜单、日期选择器等需要溢出显示的浮层。此时应改用 display: flow-root 或 clearfix。
Q4:BFC、IFC、GFC、FFC 分别是什么?它们与层叠上下文(Stacking Context)是一回事吗?
答:
| 缩写 | 全称 | 触发场景 |
|---|---|---|
| BFC | Block Formatting Context | 块级格式化上下文,见上文触发条件 |
| IFC | Inline Formatting Context | 行内元素(span、a 等)自动形成 |
| GFC | Grid Formatting Context | display: grid 的容器 |
| FFC | Flex Formatting Context | display: flex 的容器 |
与层叠上下文不是一回事:
-
格式化上下文管的是布局——盒子怎么排列、margin 怎么折叠、浮动怎么避让
-
层叠上下文管的是绘制顺序(z 轴)——谁盖住谁,由
z-index、position、opacity < 1、transform等触发
一个元素可以同时是 BFC 和层叠上下文(如 position: absolute; z-index: 1),两者维度正交,互不隶属。
Q5:场景题:卡片组件中,子元素设置了 margin-top: 20px,结果整个卡片被推下去了 20px,而不是内部留出间距。如何定位并给出至少三种修复方案?
答:这是典型的父子 margin 穿透(折叠)。卡片容器没有 border、padding 或 BFC 隔离时,第一个子元素的 margin-top 会穿透到容器外面,表现为容器整体下移。
修复方案:
/* 方案一:给容器加 padding 替代 margin */
.card { padding-top: 1px; } /* 或直接用 padding 控制间距 */
/* 方案二:给容器触发 BFC,隔离 margin 折叠 */
.card { display: flow-root; } /* 现代推荐 */
/* 或 .card { overflow: hidden; } */
/* 方案三:加边框阻断(实际少用,仅作原理演示) */
.card { border-top: 1px solid transparent; }
/* 方案四:改用 flex/grid 布局(容器成为 FFC/GFC,天然不折叠) */
.card { display: flex; flex-direction: column; }考察要点:
-
能从现象(容器整体下移)反推出 margin 穿透
-
知道阻断折叠的本质:在父子 margin 之间制造阻隔(border/padding)或建立独立格式化上下文
-
方案排序有讲究:改结构(padding)> 改格式化上下文(flow-root)> 布局重构(flex),体现对侵入性的权衡
CSS 这部分考察的很少,近两年我面试的时候很少会看到有考察 CSS 的面试,但是大厂考察基本都集中在一些有坑的地方,来判断你有没有踩过坑,大致估算你的项目难易度。