Skip to content

06 · CSS 布局体系 ​

本篇解决什么问题: CSS 布局的坑,八成不是"不会写",而是不知道浏览器按什么规则算。 本篇讲清盒模型 → BFC → Flex/Grid 分工 → 层叠上下文 → 包含块这条主线, 让你能解释现象、而不是靠 !important 和 margin-top: -1px 碰运气。

📌 与 A14(组件 API 设计)的分工:本篇讲底层规则,A14 讲样式方案选型。


开篇速览 ​

概念一句话解决什么现象
盒模型元素占多大地方由 box-sizing 决定"设了 width 100px 实际占了 120px"
BFC一块独立的渲染区域外边距合并、浮动父级高度塌陷
Flex一维布局,按轴线分配一行内的对齐与伸缩
Grid二维布局,按网格分配整体页面骨架、复杂对齐
层叠上下文z-index 的比较单位"z-index 999 还是被盖住了"
包含块百分比 / absolute 的参照系"top: 50% 到底相对谁"

一、盒模型:先决定"怎么算尺寸" ​

css
box-sizing: content-box;  /* 默认:width 只算内容区 */
box-sizing: border-box;   /* width = 内容 + padding + border(推荐) */

同一个 width: 200px; padding: 20px; border: 1px:

box-sizing实际占位
content-box200 + 20×2 + 1×2 = 242px
border-box200px(内容区被压缩到 158px)

工程约定:全局重置

css
*, *::before, *::after { box-sizing: border-box; }

💡 为什么推荐 border-box:改 padding 不会意外撑破栅格;和设计师给的"盒子尺寸"语义一致。


二、BFC:一块"自成一国"的区域 ​

BFC(Block Formatting Context,块级格式化上下文) 是一块独立的渲染区域,内部布局不影响外部。

2.1 怎么触发(记住 5 个就够) ​

css
overflow: hidden | auto | scroll;  /* 最常用,但不是 visible */
float: left | right;               /* 非 none */
position: absolute | fixed;
display: flow-root;                /* ✅ 最干净,无副作用(首选) */
display: inline-block | table-cell | flex | grid(或其内部项);

2.2 BFC 解决的三类问题 ​

① 外边距合并(Margin Collapse)

html
<div class="a">A</div>
<div class="b">B</div>
<!-- .a margin-bottom:20px; .b margin-top:30px → 间距是 30px,不是 50px -->

垂直相邻的两个块级元素,上下 margin 会合并取较大值。 解法:给其中一个包一层 BFC 容器,或用 display: flow-root。

② 父元素高度塌陷(浮动导致)

html
<div class="parent"><div class="child" style="float:left"></div></div>
<!-- .parent 高度为 0:浮动元素脱离了普通流 -->

解法(推荐顺序):

css
.parent { display: flow-root; }        /* ✅ 首选,语义清晰 */
/* 或经典的 clearfix 伪元素方案 */
.parent::after { content:''; display:block; clear:both; }

③ 自适应两栏(浮动 + BFC)

html
<div class="left" style="float:left;width:200px"></div>
<div class="right" style="overflow:hidden"></div>  <!-- BFC 不与浮动重叠 → 自适应剩余宽度 -->

⚠️ 上面这个技巧已被 Flex/Grid 取代,仅用于维护老代码。


三、Flex 与 Grid 的分工 ​

一句话选型:

一维用 Flex,二维用 Grid。 需要"同时控制行和列"就是 Grid。

维度FlexGrid
轴一维(主轴 + 交叉轴)二维(行 + 列)
思维"把元素排成一行/一列""先画格子,再把元素放进去"
适合导航栏、按钮组、卡片内元素、居中页面骨架、仪表盘、圣杯布局、表单对齐
换行flex-wrap 换行后每行独立行与列统一对齐,行列可跨

3.1 Flex:主轴与交叉轴 ​

css
.container {
  display: flex;
  flex-direction: row;         /* 主轴方向 */
  justify-content: space-between;  /* 主轴对齐 */
  align-items: center;             /* 交叉轴对齐 */
  gap: 12px;                       /* 间距(比 margin 干净) */
}
.item { flex: 1; }   /* = flex-grow:1; flex-shrink:1; flex-basis:0% */

flex: 1 的三兄弟(面试高频):

属性作用默认
flex-grow剩余空间的分配权重0(不放大)
flex-shrink空间不足时的收缩权重1(会缩)
flex-basis分配前的基准尺寸auto

💡 经典坑:flex: 1 与 flex: auto 不同——前者 basis: 0%(完全按比例分), 后者 basis: auto(先按内容宽度,再分剩余)。做等分布局要用 flex: 1。

💡 min-width: auto 陷阱:flex 子项默认 min-width: auto, 内容长时不会收缩导致撑破容器 → 需要 min-width: 0 才能触发 text-overflow: ellipsis。

3.2 Grid:先画格子 ​

css
.container {
  display: grid;
  grid-template-columns: 200px 1fr;      /* 固定侧栏 + 自适应主区 */
  grid-template-rows: auto 1fr auto;     /* header / main / footer */
  gap: 16px;
}
/* 常用套路 */
grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));  /* 响应式卡片流 */
grid-column: 1 / -1;                      /* 跨满整行 */

auto-fill vs auto-fit:容器有剩余时,auto-fill 保留空轨道,auto-fit 折叠空轨道(让现有项拉伸)。


四、层叠上下文:为什么 z-index: 9999 还是被盖住 ​

核心认知:z-index 不是全局排序,它只在同一个层叠上下文内比较。

4.1 谁会创建层叠上下文 ​

css
position: relative | absolute | fixed; z-index: 非 auto;
opacity < 1;
transform / filter / perspective / backdrop-filter: 非 none;
will-change: transform | opacity;
isolation: isolate;      /* ✅ 主动隔离,最干净 */
flex / grid 子项且 z-index 非 auto;

4.2 层叠顺序(同一上下文内,从下到上) ​

① 背景与边框(层叠上下文根元素)
② 负 z-index 子元素
③ 块级盒子(普通流)
④ 浮动盒子
⑤ 行内 / inline-block 内容
⑥ z-index: 0 / auto 的定位元素
⑦ 正 z-index 子元素(值越大越靠上)

4.3 排查套路 ​

"我设了 z-index: 9999 却还是被盖住" → 99% 是父级创建了层叠上下文, 你的元素被困在父级的层级里,父级整体比别人低,子元素再高也没用。

解法:把"要浮起来的东西"** portal 到 body 下**(弹窗、下拉菜单、Tooltip 的标准做法), 或给竞争的父级去掉 transform / opacity 等副作用属性。


五、包含块:百分比和 absolute 到底相对谁 ​

包含块(Containing Block) 是元素计算尺寸与位置的参照系。

元素的 position包含块是谁
static / relative最近的块级祖先的内容区
absolute最近的 position 非 static 的祖先的 padding 区
fixed视口(但若祖先有 transform/filter/will-change,则退化为该祖先——大坑)

推论(面试常考):

想让 position: absolute 的子元素相对某个父级定位 → 父级必须 position: relative。 想让 position: fixed 相对视口 → 祖先链上不能有 transform(否则"fixed 变 absolute")。

百分比的参照:

  • width: 50% → 相对包含块的 width
  • height: 50% → 相对包含块的 height(父级没显式高度时百分比失效)
  • padding-top: 50% / margin-top: 50% → 都相对包含块的 width(做正方形/固定比例的常用技巧)

六、几个必会的布局实现 ​

css
/* ① 水平垂直居中(现代写法) */
.center { display: grid; place-items: center; }
/* 或 */
.center { display: flex; align-items: center; justify-content: center; }

/* ② 固定宽高比卡片 */
.ratio { aspect-ratio: 16 / 9; }

/* ③ 粘性页脚(内容不足时 footer 也贴底) */
body { display: flex; flex-direction: column; min-height: 100vh; }
main  { flex: 1; }

/* ④ 单行省略 / 多行省略 */
.ellipsis   { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.line-clamp { display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2; overflow: hidden; }

/* ⑤ 响应式容器(现代写法,逐步替代媒体查询) */
@container card (min-width: 400px) { .title { font-size: 20px } }

自检清单 ​

  • [ ] 能说出 content-box 与 border-box 的实际占位差异,并知道工程上该选哪个
  • [ ] 能说出 BFC 的 5 种触发方式与它解决的 3 类问题
  • [ ] 能说清 Flex 与 Grid 的分工判据,以及 flex: 1 与 flex: auto 的区别
  • [ ] 能解释 "z-index: 9999 却被盖住" 的根因并提出 portal 方案
  • [ ] 能说出 absolute 与 fixed 的包含块分别是什么,以及 fixed 失效的常见原因

常见误区 ​

❌ 误区✅ 事实
"z-index 是全局的"只在同一层叠上下文内可比
"Flex 能搞定所有布局"需要行列同时对齐时,Grid 才对
"vertical-align 能垂直居中块级元素"它只对行内 / table-cell 有效
"百分比高度总是生效"父级没有显式高度时,百分比 height 失效

动手练习 ​

  1. 用 Grid 实现一个"圣杯布局"(顶栏 + 左右侧栏自适应 + 主区 + 底栏),要求主区优先加载。
  2. 造一个"z-index 失效"的最小复现(父级 transform + 子元素高 z-index),然后用 portal 思路修好。
  3. 用 @container 实现同一个卡片组件在窄栏/宽栏下自动切换布局。

相关文档 ​