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% 到底相对谁" |
一、盒模型:先决定"怎么算尺寸"
box-sizing: content-box; /* 默认:width 只算内容区 */
box-sizing: border-box; /* width = 内容 + padding + border(推荐) */同一个 width: 200px; padding: 20px; border: 1px:
| box-sizing | 实际占位 |
|---|---|
content-box | 200 + 20×2 + 1×2 = 242px |
border-box | 200px(内容区被压缩到 158px) |
工程约定:全局重置
*, *::before, *::after { box-sizing: border-box; }💡 为什么推荐
border-box:改 padding 不会意外撑破栅格;和设计师给的"盒子尺寸"语义一致。
二、BFC:一块"自成一国"的区域
BFC(Block Formatting Context,块级格式化上下文) 是一块独立的渲染区域,内部布局不影响外部。
2.1 怎么触发(记住 5 个就够)
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)
<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。
② 父元素高度塌陷(浮动导致)
<div class="parent"><div class="child" style="float:left"></div></div>
<!-- .parent 高度为 0:浮动元素脱离了普通流 -->解法(推荐顺序):
.parent { display: flow-root; } /* ✅ 首选,语义清晰 */
/* 或经典的 clearfix 伪元素方案 */
.parent::after { content:''; display:block; clear:both; }③ 自适应两栏(浮动 + BFC)
<div class="left" style="float:left;width:200px"></div>
<div class="right" style="overflow:hidden"></div> <!-- BFC 不与浮动重叠 → 自适应剩余宽度 -->⚠️ 上面这个技巧已被 Flex/Grid 取代,仅用于维护老代码。
三、Flex 与 Grid 的分工
一句话选型:
一维用 Flex,二维用 Grid。 需要"同时控制行和列"就是 Grid。
| 维度 | Flex | Grid |
|---|---|---|
| 轴 | 一维(主轴 + 交叉轴) | 二维(行 + 列) |
| 思维 | "把元素排成一行/一列" | "先画格子,再把元素放进去" |
| 适合 | 导航栏、按钮组、卡片内元素、居中 | 页面骨架、仪表盘、圣杯布局、表单对齐 |
| 换行 | flex-wrap 换行后每行独立 | 行与列统一对齐,行列可跨 |
3.1 Flex:主轴与交叉轴
.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:先画格子
.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-fillvsauto-fit:容器有剩余时,auto-fill保留空轨道,auto-fit折叠空轨道(让现有项拉伸)。
四、层叠上下文:为什么 z-index: 9999 还是被盖住
核心认知:z-index 不是全局排序,它只在同一个层叠上下文内比较。
4.1 谁会创建层叠上下文
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%→ 相对包含块的 widthheight: 50%→ 相对包含块的 height(父级没显式高度时百分比失效)padding-top: 50%/margin-top: 50%→ 都相对包含块的 width(做正方形/固定比例的常用技巧)
六、几个必会的布局实现
/* ① 水平垂直居中(现代写法) */
.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 失效 |
动手练习
- 用 Grid 实现一个"圣杯布局"(顶栏 + 左右侧栏自适应 + 主区 + 底栏),要求主区优先加载。
- 造一个"z-index 失效"的最小复现(父级
transform+ 子元素高 z-index),然后用 portal 思路修好。 - 用
@container实现同一个卡片组件在窄栏/宽栏下自动切换布局。
相关文档
- 样式方案选型(CSS-in-JS vs 原子化)→ A14 · 组件 API 设计与设计系统
- 布局抖动与重排优化 → A10 · Web 性能优化
- 渲染流水线(布局→绘制→合成)→ A04 · 浏览器与网络