【每日一面】盒模型与 BFC

2026-09-28 16 min 5604 字 -- 次阅读
摘要

盒模型由 content、padding、border、margin 四层组成,box-sizing 决定宽度算到哪;BFC 是封闭的渲染区域,管住 margin 折叠、清浮动和自适应两栏布局。

基础问答

Q1:CSS 盒模型是什么?有哪两种?区别在哪?

CSS 盒模型是浏览器渲染每个元素时的矩形盒子,由内到外分为四部分:content(内容)→ padding(内边距)→ border(边框)→ margin(外边距)。

两种盒模型:

模型box-sizing 值宽度计算方式
标准盒模型(W3C 默认)content-boxwidth = content 宽度,padding/border 会额外撑大盒子
IE 盒模型(怪异模式)border-boxwidth = content + padding + border,padding/border 包含在设定宽度内
css
/* 标准盒模型:实际占位 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),常见三种场景:

  1. 相邻兄弟元素:上一个的 margin-bottom 与下一个的 margin-top 合并,取较大者

  2. 父子元素:子元素的 margin-top 穿透到父元素外面(父元素无边框、无 padding、无 BFC 隔离时)

  3. 空块元素:自身上下 margin 合并,高度可能不生效

css
/* 相邻兄弟:实际间距是 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 的核心规则有三条:

  1. 内部盒子垂直方向一个接一个排列,且 margin 会折叠

  2. BFC 区域不会与浮动元素重叠

  3. 计算 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
  1. 解决 margin 穿透:给父元素触发 BFC,子元素的 margin 就被限制在 BFC 内部:

    css
    .parent {
      overflow: hidden; /* 触发 BFC,子元素 margin-top 不再穿透 */
    }
  2. 清除浮动(高度塌陷):浮动元素脱离文档流,父元素高度变为 0。父元素触发 BFC 后,计算高度时会把浮动子元素算进去:

    css
    .container {
      display: flow-root; /* 最干净的清浮动方式 */
    }
  3. 两栏布局:左栏浮动,右栏触发 BFC,右栏不再与浮动重叠,实现自适应:

    css
    .left  { float: left; width: 200px; }
    .right { overflow: hidden; /* BFC,自动避开左栏宽度 */ }

BFC 实战

两栏布局加清除浮动

xml
<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-rootdisplay: flow-root显式创建 BFCIE 不支持⭐⭐⭐⭐⭐ 现代项目首选
overflowoverflow: hidden触发 BFC全兼容⭐⭐⭐⭐ 注意溢出裁剪
clearfix.clearfix::after{content:"";display:block;clear:both}伪元素撑开全兼容⭐⭐⭐ 老牌经典
flex/grid容器改 flex/grid子元素不再浮动现代浏览器⭐⭐⭐⭐ 顺手方案

面试追问

Q1:下面代码中,两个盒子的实际渲染宽度分别是多少?

xml
<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?

xml
<style>
  .top { margin-bottom: 30px; }
  .bottom { margin-top: 30px; }
</style>
<div class="top">上</div>
<div class="bottom">下</div>

答:实际间距是 30px(相邻兄弟垂直 margin 合并,取较大者)。

让间距变成 60px 的常见方法:

  1. 把其中一个元素放进 BFC 容器中(如 display: flow-root 的 wrapper),切断合并关系

  2. 只保留一个方向的 margin(如只用 margin-bottom: 60px)

  3. 改用 padding 实现间距

css
/* 方案一:BFC 隔离 */
.wrapper { display: flow-root; }
/* HTML: <div class="wrapper"><div class="top">上</div></div> */

Q3:overflow: hidden 清除浮动有什么副作用?什么场景下不能用?

答:overflow: hidden 触发 BFC 清浮动的副作用有两个

  1. 内容裁剪:子元素超出容器部分(如绝对定位的下拉菜单、tooltip、负 margin 布局)会被直接裁掉

  2. 层级/滚动语义变化:overflow: hidden 会使元素成为新的包含块和裁剪上下文,可能改变 position: absolute 子元素的定位基准,也会阻止内部滚动

不能用的典型场景主要是容器内有下拉菜单、日期选择器等需要溢出显示的浮层。此时应改用 display: flow-root 或 clearfix。

Q4:BFC、IFC、GFC、FFC 分别是什么?它们与层叠上下文(Stacking Context)是一回事吗?

答:

缩写全称触发场景
BFCBlock Formatting Context块级格式化上下文,见上文触发条件
IFCInline Formatting Context行内元素(span、a 等)自动形成
GFCGrid Formatting Contextdisplay: grid 的容器
FFCFlex Formatting Contextdisplay: 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 会穿透到容器外面,表现为容器整体下移。

修复方案:

css
/* 方案一:给容器加 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 的面试,但是大厂考察基本都集中在一些有坑的地方,来判断你有没有踩过坑,大致估算你的项目难易度。

评论