第 3 章
现代布局
Flex、Grid、容器查询、子网格——99% 的页面布局靠这套
现代 CSS 让"两栏自适应""卡片网格""主轴居中"从靠 hack 解决变成 一两行声明就能搞定。代价是:你得知道哪种场景该选哪一套。
这一节不讲 Flex / Grid 的每一个属性——MDN 已经做得很好了。本节讲: 遇到一个布局需求时怎么判断该用什么、AI 写出来后怎么 review、 2026 年视角下哪些新特性值得立刻用上。
怎么读这一节(建议):
你是 直接读 新人 / 转行者 §0 简史 → §1 选型 → §2 Flex 五姿势 → §12 DevTools 多年 Flex 一把梭,最近没关注 CSS §1 §1.3「老 Flex 用户的 5 分钟升级路径」直接看 想做现代布局(吸顶 / 瀑布流 / 比例图) §7 现代布局速查 要 AI 帮写布局,想知道自己看什么 §10 「AI 时代写布局的最小集」
通读约 1 小时;按上述子节挑读 15-20 分钟。没必要一次读完——
写代码遇到具体问题再回查更有用。
0. 一段必要的回顾:CSS 布局简史
要理解今天的 Flex / Grid 为什么这样设计,先看看它们替换了什么。 每一代布局方案都在解决上一代留下的问题,这条线索串起来,新写法 就不再是"凭空记忆"。
| 年代 | 主流布局手段 | 解决了什么 | 留下了什么 |
|---|---|---|---|
| ~1996 | <table> 排版 | 第一次能做"几行几列" | HTML 被表现职责绑死、可访问性灾难、改一处全乱 |
| ~2003 | float + clear | 终于能用 CSS 控制布局 | 父元素塌陷(要 clearfix hack)、垂直居中靠"魔法 margin"、做整页布局极痛苦 |
| ~2009 | display: inline-block | 不用 float 也能水平排列 | 元素之间出现"幽灵空格"(HTML 里的换行被当空白);间距控制要靠 font-size: 0 这种 hack |
| ~2017 | Flexbox(全支持) | 一维布局有了"主轴"心智 | 二维场景仍要嵌套;最初没有 gap(后来补上了) |
| ~2017 | Grid(全支持) | 二维布局首次原生可表达 | 学习曲线略陡;嵌套对齐还得等 subgrid |
| ~2023 | 容器查询 + subgrid | 组件化时代的"按自身响应" | —(这一代到目前为止没明显遗憾) |
读这张表能感受到一件事:CSS 不是"忽然变好"的,是几代人填了几代 坑后才到今天。这也是为什么本章不教浮动布局——那不是"古典之美", 是被淘汰的方案。
老代码里你仍然会见到
clearfix、::after { content: ""; clear: both }、display: table——这些都是上表里"留下了什么"那一列的化石。读得 懂它们在干嘛,但不要用它们写新代码。
这跟"我现在该怎么写"有什么关系
下面讲到任何一个新写法时,如果它替换了某种旧 hack,本节都会用 ❌ / ✅ 对比给你看——这是网上多数 CSS 教程不写的部分,但它能 让你真正记住为什么是这样。
1. 一张选型决策图
来了一个布局需求,先按这棵树问:
要排什么?
├─ 一维(一行 / 一列里铺东西)
│ └─ Flex
│
├─ 二维(行和列都有结构)
│ └─ Grid
│
├─ 整页布局(header / sidebar / main / footer)
│ └─ Grid(用 `grid-template-areas` 命名区域)
│
└─ 文字内容流(段落 / 列表)
└─ 不动布局,让正常文档流(normal flow)就好最常见的错是反过来——用 Grid 排一行按钮、用 Flex 拼整页布局。 都能跑,但用反了之后改起来很累。
1.1 现实承认:Flex 一把梭能解决 80% 场景
先说一个网上 CSS 教程不太愿意承认的事实:业界相当一部分团队
日常开发只用 Flex 也能跑得动。一行多列、多行多列(配 flex-wrap)、
垂直居中、分栏布局——Flex 全能做,做得也不算难看。
所以"Flex 一把梭"不是错的——它是有代价的,但代价不一定每个 项目都付得起。需要看清楚两件事:
- Flex 一把梭的代价:嵌套层数会变深(外层主轴 → 内层交叉轴 → 再外层);多行场景里"列对齐"靠每个 item 自己设宽度,加 / 减一行就要调;做整页布局时 header / sidebar / footer 的嵌套 HTML 比 Grid 多一倍
- Grid 真正不可替代的就 5 类场景(其他场景 Flex 也行):
| # | 场景 | 为什么 Flex 做不动 |
|---|---|---|
| 1 | 卡片网格自动改列数(auto-fill / minmax) | Flex 的 flex-wrap 不能做"按容器宽度自动决定列数" |
| 2 | 整页 header / sidebar / main / footer | grid-template-areas 的 ASCII 直观度 Flex 嵌套写法替代不了 |
| 3 | 跨行 / 跨列对齐(订单详情、表格类视图) | Flex 没有"行轨道"概念,多行无法跨项对齐 |
| 4 | 子网格嵌套对齐(卡片标题 / 内容 / 底齐) | subgrid 能让嵌套元素对齐到外层轨道,Flex 完全没有等价方案 |
| 5 | 显式坐标定位(仪表盘、画布类布局) | grid-column: 2 / 5 / grid-row: span 3 这种"放在第几格" |
其他所有场景,Flex 真的够用——尤其是导航栏、按钮组、表单行、 弹窗内容、移动端列表这类一维布局,硬上 Grid 反而是炫技。
1.2 选型简化版
不想记 5 类场景?记 3 句话就够:
- 一维(一行或一列) → Flex
- 整页大布局 + 卡片网格自动列数 → Grid
- 以上都不是 → Flex 一把梭就行,不要纠结
| 场景 | 推荐 | 为什么不是另一个 |
|---|---|---|
| 导航栏(一行多个按钮) | Flex | Grid 也能做,但 Flex 的"主轴对齐"语义更贴近"一排东西" |
| 卡片网格(多行多列) | Grid | Flex 的换行需要算 flex-basis、间距控制变形;Grid 一行 repeat() 搞定 |
| 整页 header / sidebar / footer | Grid + grid-template-areas | Flex 嵌套地狱;Grid 命名区域读起来像 ASCII 画 |
| 表单一组 label + input | Grid(两列) | 多个 input 的右列会自然对齐;Flex 要给 label 加固定宽度 |
| 单个元素居中 | Flex 或 place-items: center | Grid 也行;这是最不重要的选型 |
1.3 老 Flex 用户的 5 分钟升级路径
如果你过去几年一直用 Flex 解决一切、最近没系统过 CSS,不必从头学 Grid——按下面的顺序补就够:
- 学 1 个 Grid 写法:
grid-template-columns: repeat(auto-fill, minmax(240px, 1fr))和gap: 1rem——这一行是卡片网格的现代答案,记住这一行就解锁 80% 的 Grid 价值 - 学 1 个语法糖:
grid-template-areas——做整页布局时比嵌套 Flex 干净一倍,代码读起来像 ASCII 画 - 遇到上面 §1.1 的 5 类场景再深入:日常 Flex 写得顺就继续 Flex, 不需要"升级到 Grid"——选型是结果不是目标
反过来不成立:老 Grid 用户不需要"补 Flex"——Flex 心智更简单, 看一眼例子就会。
设计判断(声明式 vs 命令式):
display: grid是声明式—— 你说"我要 3 列、间距 1rem",浏览器决定每个 item 落在哪。旧式靠 JS 计算坐标 +position: absolute是命令式——你手把手算。 AI 写声明式 CSS 比命令式 JS 布局稳得多,因为浏览器接管了边界 情况的调度。何时该升级到 Grid 而不是嵌套 Flex,是人的判断。
2. Flexbox:一维布局的瑞士军刀
2.1 主轴 / 交叉轴心智
Flex 的所有困惑都来自不分清"主轴"和"交叉轴":
column 时两者交换| 想做什么 | 主轴属性 | 交叉轴属性 |
|---|---|---|
| 元素在主轴上的分布 | justify-content | — |
| 元素在交叉轴上对齐 | — | align-items |
| 单个元素覆盖默认对齐 | — | align-self |
记住这张图,Flex 的 90% 困惑就消失了。flex-direction: column
时主轴变成纵向,但属性名不变——这是新人最容易卡的点。
词源解读:为什么是
justify和align,不是horizontal/vertical? 这是 CSS 多年被反复问的问题——其实英语本义就藏着 答案:
- justify(动词)= "对齐 / 调整使齐整"——印刷排版里的术语, 指把一行文字两端撑齐(你在 Word 里看到的"两端对齐"按钮就 叫 Justify)。所以
justify-content处理的是主轴方向上"内容 怎么沿这条线撑开 / 收紧"——flex-start/center/space-between/space-around都是在描述"沿主轴这条线"的分布- align(动词)= "排成一列 / 对齐"——日常英语里"align with" 就是"和某物对齐"。
align-items处理的是交叉轴方向上"item 们要不要对齐到同一条线"——flex-start/center/stretch/baseline都是在描述"对齐到哪条线"所以这套命名跟方向(横纵)无关,跟动作(撑开 vs 对齐)有关。 当
flex-direction: column时,主轴变纵向,但"撑开主轴"还是justify-content的活——属性名不需要变,因为它本来就不是说 "横"或"纵"。CSS Grid 也用同一套术语。一手参考: CSS Box Alignment Module Level 3 是这套属性的规范源头——
justify-*管"沿主/inline 轴的分布",align-*管"沿交叉/block 轴的对齐",这套语义在 Flex / Grid / 多列布局里完全统一。理解一次,全都通了。
2.2 最常用 5 种姿势
/* 1. 水平 + 垂直居中 */
.center {
display: flex;
justify-content: center;
align-items: center;
}
/* 2. 平均分布(导航 / 工具栏) */
.toolbar {
display: flex;
justify-content: space-between;
}
/* 3. 自适应剩余空间(输入框 + 按钮) */
.row {
display: flex;
gap: 0.5rem;
}
.row input {
flex: 1; /* 撑满剩余空间 */
}
/* 4. 自动换行(标签云) */
.tags {
display: flex;
flex-wrap: wrap;
gap: 0.5rem;
}
/* 5. 等宽列 */
.cols {
display: flex;
gap: 1rem;
}
.cols > * {
flex: 1;
}会写这 5 种,业务里 80% 的 Flex 场景就够用了。
以前怎么做:第 1 种"水平 + 垂直居中"在 Flex 普及之前是著名 难题。常见解法是
position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%)——本质是"先把元素左上角放到中心,再用 transform 往回挪自身一半"。 能跑,但脱离文档流、影响父容器尺寸计算、嵌套时易冲突。Flex 把这件 事压到 3 行声明,是 2017 年之后整个行业的"幸福感升级"之一。
2.3 gap 是现代写法
旧代码用 margin-right + :last-child { margin-right: 0 } 制造间距——
这是 IE / 旧版 Safari 时代留下的肌肉记忆。现代浏览器在 Flex / Grid
都支持 gap,统一用 gap,再也不用算 :last-child。
词源 + 设计意图:
gap英语就是"缝隙 / 间隙",比margin(= 页边距,本来描述的是"元素相对周围的留白")更精确。margin解决"我和邻居距离多远"是间接的——两个元素之间的实际距离取决于 各自的 margin 怎么算(还可能折叠!)。gap由容器直接定义 "子元素之间的缝隙宽度",只在子元素之间生效,不影响容器边缘。 这是责任的迁移:从"每个孩子负责自己周围的距离"变成"父母统一规 定孩子之间隔多远"——这也是为什么现代项目里很少再写margin-right。 一手参考:MDN: gap。
/* ❌ 旧 */
.list > * {
margin-right: 1rem;
}
.list > *:last-child {
margin-right: 0;
}
/* ✅ 新 */
.list {
display: flex;
gap: 1rem;
}3. Grid:二维布局的现代答案
3.1 最小心智
Grid 的核心就两件事:
- 定义轨道(tracks):用
grid-template-columns/grid-template-rows声明几行几列、每条轨道多宽 - 放置元素(placement):让浏览器自动放,或用
grid-column/grid-row/grid-area显式指定
.gallery {
display: grid;
grid-template-columns: repeat(3, 1fr); /* 3 等分列 */
gap: 1rem;
}fr 是"剩余空间的份数"——3 个 1fr = 平均分。这是 Grid 独有的单位,
Flex 里没有。
词源 + 本质原理:
fr到底是什么?fr是 fraction 的 缩写,英语意思就是"分数 / 一份"。它不是固定长度单位(不像px/rem/%),而是"剩余可分配空间的一份"。关键在于"剩余"两个字。Grid 计算列宽时分两步:
- 先扣固定尺寸:所有
px/rem/%/auto/min-content等"非 fr"轨道,按它们的需求先占据空间- 再分剩余空间:把容器剩下的宽度,按
fr的数值比例分给 所有fr轨道所以
grid-template-columns: 200px 1fr 2fr在 1000px 容器里: 先扣掉 200px,剩 800px;1fr : 2fr = 1 : 2,于是分得 ≈ 267px 和 ≈ 533px。这也是为什么
repeat(3, 1fr)不等于repeat(3, 33.33%)——百分比 是"占容器总宽的 1/3",遇上gap就会溢出(3 × 33.33% + 2 × gap > 100%);而1fr是"占容器减去 gap 后剩余空间的 1/3", 永远不会溢出。这就是fr真正解决的问题。一手参考: CSS Grid Layout Level 1 §7.2.3 Flexible Lengths: the fr unit
3.2 自适应卡片网格(必会姿势)
业务里出现频率最高的 Grid 用法:
.cards {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
gap: 1rem;
}读法:「每列至少 240px,能放几列放几列,剩下的平均分」。这一行 取代了所有"768px 以下两列、1024px 以下三列、再宽就四列"的媒体 查询写法。
以前怎么做:在
auto-fill + minmax出现之前,"卡片随容器宽 度自动改列数"几乎只能靠媒体查询硬列:.cards { display: grid; grid-template-columns: 1fr; gap: 1rem; } @media (min-width: 480px) { .cards { grid-template-columns: repeat(2, 1fr); } } @media (min-width: 768px) { .cards { grid-template-columns: repeat(3, 1fr); } } @media (min-width: 1024px) { .cards { grid-template-columns: repeat(4, 1fr); } }缺点:断点和卡片宽度耦合——产品改"卡片至少 280px"就要重算 所有断点。
auto-fill + minmax(240px, 1fr)把规则改成"声明卡片 宽度下限,让浏览器决定列数",这正是设计判断里"声明式 vs 命令式" 的活案例。
auto-fill vs auto-fit 的区别:
| 取值 | 容器够宽且 item 不足时 | 适用场景 |
|---|---|---|
auto-fill | 留空轨道(visible empty tracks) | 大多数情况;item 自然左对齐 |
auto-fit | 把空轨道折叠(collapse),item 撑满 | 想让现有 item 撑满整行时 |
只有少数 item 时差别明显——多数场景两者表现一样,记不清就用
auto-fill。
词源解读:
fill和fit字面差什么? 英语本义就是这两个 关键字差异的最佳记忆法:
- fill(动词)= "填满"——像把果汁倒进杯子,有多少容量就 倒多少。
auto-fill就是这个意思:浏览器尽可能"填满"轨道 位置,没东西占的就是空轨道(empty track),格子留着- fit(动词)= "贴合 / 适配"——像把衣服改尺寸到正好合身。
auto-fit就是"让现有 item 适配可用空间",多余的轨道折叠 消失,剩下的 item 撑满整行一句话:fill 是"留位置",fit 是"撑空间"。一手参考: CSS Grid Level 1 §7.2.2 The repeat() notation
3.3 整页布局:grid-template-areas
经典的"header / sidebar / main / footer":
.page {
display: grid;
grid-template-columns: 240px 1fr;
grid-template-rows: auto 1fr auto;
grid-template-areas:
"header header"
"sidebar main"
"footer footer";
min-height: 100vh;
}
.page > header {
grid-area: header;
}
.page > nav {
grid-area: sidebar;
}
.page > main {
grid-area: main;
}
.page > footer {
grid-area: footer;
}grid-template-areas 的字符串本身就是一张 ASCII 画——读 CSS 时立刻
能看懂版面。这是 Flex 完全做不到的可读性。
3.4 显式 vs 隐式轨道
定义了 3 列结果放进 5 个 item,多出来的怎么办?浏览器自动开新行—— 这叫"隐式轨道"。
.grid {
display: grid;
grid-template-columns: 1fr 1fr 1fr; /* 显式:3 列 */
grid-auto-rows: 200px; /* 隐式行的高度 */
gap: 1rem;
}不写 grid-auto-rows 时,隐式行高度由内容决定(auto)——常见的
"卡片高度参差"就是没设它的后果。
4. 容器查询:组件化时代的响应式
4.1 为什么需要容器查询
媒体查询(@media)响应视口(viewport)——整个浏览器窗口宽度。
它的问题是:同一个组件在不同位置宽度可能不同。
举例:一个"卡片"组件,放在主内容区里有 800px 宽,放在侧栏里只有 240px 宽。媒体查询只能根据"屏幕宽度"决定它的样式,不知道它实际 被嵌在哪里。
容器查询(@container)解决这个:响应组件自身所在容器的宽度。
/* 1. 父容器声明自己是查询源 */
.card-grid {
container-type: inline-size;
/* 也可以给个名字便于嵌套场景 */
container-name: cards;
}
/* 2. 子组件按"容器宽度"响应,而不是"视口宽度" */
.card {
display: grid;
grid-template-columns: 1fr;
}
@container cards (min-width: 480px) {
.card {
grid-template-columns: 120px 1fr; /* 缩略图 + 内容 */
}
}读法:「当 cards 容器自身宽度 ≥ 480px 时,卡片切成左右两栏」。
这个组件不管放在哪,都按自身可用宽度调整——这才是真正的
"组件化响应式"。
4.2 何时用容器查询、何时仍用媒体查询
| 场景 | 用什么 |
|---|---|
| 整页布局(侧栏在窄屏收起) | 媒体查询 |
| 导航栏在小屏变汉堡菜单 | 媒体查询 |
| 卡片 / 表格 / 通用组件的内部布局 | 容器查询 |
| 同一组件可能出现在主区或侧栏 | 容器查询 |
| 字号 / 行距按视口缩放 | 媒体查询 |
经验法则:组件库 / 设计系统里的组件 → 容器查询;页面级布局 → 媒体查询。两者并用,不是替代关系。
4.3 兼容性
容器查询在 Chrome 105 / Safari 16 / Firefox 110 后稳定。2026 年 新项目可以无脑用。需要兼容更老浏览器的项目按业务实际再说。
5. 子网格:让嵌套对齐"贯通"
5.1 问题:嵌套 Grid 的对齐断层
经典案例:一行卡片,每张卡片内部分 title / body / footer 三段。 如果 title 长度不一,不同卡片的 body 起始线就对不齐——因为每张 卡片是独立的 Grid,互不相通。
5.2 子网格(subgrid)的解法
让卡片继承外层 Grid 的轨道定义:
.row {
display: grid;
grid-template-columns: repeat(3, 1fr);
grid-template-rows: auto 1fr auto;
gap: 1rem;
}
.card {
display: grid;
grid-row: span 3; /* 占满外层 3 行 */
grid-template-rows: subgrid; /* 关键:继承外层行轨道 */
}每张卡片的 title / body / footer 落在同一根轨道上,跨卡片 天然对齐。
5.3 兼容性 + 何时用
子网格在 Chrome 117 / Safari 16 / Firefox 71 后稳定。用得没那么频繁—— 日常布局靠 Flex + Grid 就够了,遇到"卡片嵌套对齐断层"再上 subgrid。 知道存在 + 何时拿出来用即可。
6. 居中难题:现代答案速查
"垂直居中"在 2010 年代是面试题——现代 CSS 已经把它压成 1-2 行:
/* 1. Flex 居中(最常用) */
.center {
display: flex;
justify-content: center;
align-items: center;
}
/* 2. Grid 一行流 */
.center {
display: grid;
place-items: center;
}
/* 3. 绝对定位 + transform(覆盖层 / 弹窗) */
.modal {
position: absolute;
inset: 0; /* 等价 top/right/bottom/left: 0 */
margin: auto;
}
/* 4. 单行文字 */
.button {
line-height: 40px; /* 等于元素高度 */
text-align: center;
}| 场景 | 用哪个 |
|---|---|
| 容器内单 / 多元素居中 | Flex 或 place-items |
| 弹窗 / 全屏覆盖层居中 | position: absolute; inset: 0; margin: auto |
| 单行按钮文字居中 | line-height + text-align: center |
7. 现代布局速查:吸顶 / 瀑布流 / 比例图 / 滚动捕捉
下面这几个语法 2024-2025 年才在所有主流浏览器稳定,业务里出现 频率很高但很多教程没跟上。每条只给最小心智 + 一段可粘贴的代码, 不展开。
7.1 position: sticky:吸顶 / 吸底导航
"滚到这个元素时它粘住、滚出可视区时它跟着滚"——以前要靠
scroll事件 +position: fixed切换,现在一行 CSS 就够。
.sticky-header {
position: sticky;
top: 0; /* 距离顶部 0 时粘住 */
z-index: 10;
background: white;
}关键约束:父元素不能有 overflow: hidden / auto / scroll
(会困住 sticky 元素);父元素必须比 sticky 元素高,否则没有
"粘住空间"。这两条是新人 90% 翻车的原因。
适用场景:分类导航、表头、表单 section 标题、左侧目录。一手参考: MDN: position。
7.2 aspect-ratio:固定宽高比的图 / 视频
"我要一个永远 16:9 的视频卡片,宽度自适应"——以前靠
padding-bottom: 56.25%hack(计算 9÷16),现在直接声明。
.video-card {
width: 100%;
aspect-ratio: 16 / 9; /* 也可写 1.778 */
background: #000;
}
.avatar {
width: 48px;
aspect-ratio: 1; /* 正方形 */
border-radius: 50%;
}用途:视频缩略图、商品主图、头像、Banner、卡片占位——所有"宽 变高也跟着变"的场景。同时避免 CLS(Cumulative Layout Shift): 图片加载前就把空间占住,不会跳动。一手参考: MDN: aspect-ratio。
7.3 瀑布流(masonry):现状与三种方案
"小红书 / Pinterest 那种参差不齐的卡片流"——CSS 现在有三条路, 按"现在能用"程度排序:
方案 A:CSS 多列(column-count)——现在就能用
.waterfall {
column-count: 3;
column-gap: 1rem;
}
.waterfall > * {
break-inside: avoid; /* 防止单个卡片被切到两列 */
margin-bottom: 1rem; /* 多列布局里不用 gap,用 margin */
}
@media (max-width: 768px) {
.waterfall {
column-count: 2;
}
}兼容所有现代浏览器。唯一限制:item 是按"列优先 → 自上而下" 排的,不是"行优先"。要"先填左边一列再填右边"就用它;要"按时间 顺序填"它就不合适。
方案 B:CSS Grid masonry(grid-template-rows: masonry)——
Safari 已支持,2026 年仍在等 Chrome / Firefox
.waterfall {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
grid-template-rows: masonry; /* 实验性 */
gap: 1rem;
}按"行优先"自动排版的现代答案——但不要在生产用。截至 2026 年 只有 Safari 默认开启,Chrome 仍在 flag 后。一手参考: MDN: masonry layout
方案 C:JS 库(Masonry.js / 自家算法)——A 不够、B 不能用时的 兜底。本手册不展开。
怎么选:业务能接受"列优先"用 A;非要"行优先"且小红书 / 移动 端为主就 C;2027+ 等 B 全平台落地后回来重写。
7.4 scroll-snap:滚动捕捉
"滑到下一张图片时自动停在边界"——轮播图、移动端横滑卡片的现代答案。
.snap-container {
display: flex;
overflow-x: auto;
scroll-snap-type: x mandatory; /* x 轴强制捕捉 */
gap: 1rem;
}
.snap-container > * {
scroll-snap-align: start;
flex: 0 0 80%; /* 每张占容器 80% 宽 */
}适用场景:移动端横滑卡片、图片轮播、整页滚动(scroll-snap-type: y mandatory 配合 100vh item)、引导页。一手参考:
MDN: CSS scroll snap。
7.5 overscroll-behavior:滚动链阻断
"弹窗里滚到底之后,背后页面不要跟着滚"——一行解决移动端最常见 的弹窗滚动穿透问题。
.modal-content {
overflow: auto;
overscroll-behavior: contain; /* 滚动不传给父元素 */
}值:auto(默认)/ contain(不传播但仍触发"刷新页面"等浏览器
默认行为)/ none(连默认行为都禁用)。一手参考:
MDN: overscroll-behavior。
7.6 dvh / svh / lvh:移动端视口高度的现代答案
"100vh 在 iOS Safari 里会被工具栏切掉一截"——从 2022 起 CSS 给了 三个新单位:
| 单位 | 全称 | 行为 |
|---|---|---|
vh | viewport height | 老单位;移动端会被工具栏遮挡,给"理论最大值" |
svh | small viewport height | "工具栏全部展开时"的可视高度——最保守值 |
lvh | large viewport height | "工具栏全部收起时"的可视高度——最宽松值 |
dvh | dynamic viewport height | 跟着工具栏展开 / 收起实时变化——最常用 |
业务里默认用 dvh:
.full-screen {
height: 100dvh; /* 不再被 iOS 工具栏切掉 */
}一手参考: MDN: 视口长度单位。
8. 间距:gap + 逻辑属性
8.1 全用 gap,告别 margin 算 :last-child
gap 在 Flex / Grid / 多列布局都生效。新项目里几乎不再写
margin-right / margin-bottom 来造间距。
8.2 逻辑属性(logical properties)
margin-left / margin-right 在国际化时会出问题——阿拉伯语 / 希伯来语
是从右往左排版(RTL),这时"左"应该变成"右"。
逻辑属性把"物理方向"换成"逻辑方向":
| 物理属性 | 逻辑属性 |
|---|---|
margin-left | margin-inline-start |
margin-right | margin-inline-end |
margin-top | margin-block-start |
margin-bottom | margin-block-end |
width | inline-size |
height | block-size |
padding-left/right | padding-inline |
中文站点暂时用不到 RTL,但**padding-inline / margin-block 这种
"上下 / 左右合并"的简写比 padding: 0 16px 读起来更清晰**——养成
逻辑属性的肌肉记忆,未来做多语言不用大改。
/* 旧 */
.card {
padding: 0 16px;
margin: 8px 0;
}
/* 新 */
.card {
padding-inline: 16px;
margin-block: 8px;
}9. 中文 Web 的两个布局陷阱
写中文站点会踩到两个英文社区很少提的坑——本节单独点破。
9.1 text-wrap: pretty / balance 在中文场景反常
text-wrap: pretty(CSS Text Module Level 4)是为英文段落设计的——
它让浏览器主动调整换行避免末行只有一两个单词("orphan")。
但中文每个汉字都是潜在断点,pretty 算法会在意想不到的位置换行。
叠加 overflow-wrap: anywhere 后,结果就是用户看到的"无规律换行"。
踩坑案例 + 修复:本仓库 journal/2026-05-21
有完整复盘——叠加三个换行属性导致段落"看起来有些行提前换行",
最终回退到只用 overflow-wrap: break-word。
经验法则:
/* ❌ 中文段落不要默认引入 */
p {
text-wrap: pretty;
word-break: break-word;
overflow-wrap: anywhere;
}
/* ✅ 默认就够 */
p {
overflow-wrap: break-word;
}要用 text-wrap: pretty 必须先在中文段落实测——它的设计目标几乎
全是英文场景。
9.2 全角标点的"半个空格"
中文混排英文 / 数字时,全角标点(、,。:)后面会有一个视觉上的 "半角空"。常见解法:
text-spacing-trim(实验性):CSS Text Module Level 4 的 原生答案,把全角标点之间的多余空白裁掉。Chrome 123+ 支持, Safari / Firefox 仍在路上- 手动调整字符间距:用
letter-spacing微调,写起来麻烦 - 不管它:现代浏览器多数情况显示得也还行——业务里先验证 实际效果再决定要不要修
这两条都验证了一个普适经验:CSS 新特性默认为英文设计, 中文场景必须单独验证。详见 §3.4 §3 CJK 排版。
10. AI 时代写布局的最小集:人到底该看什么
这一节是手册区别于网上 CSS 教程的核心。
诚实承认一个事实:2026 年的现实是,多数布局代码 AI 已经写得很好—— 你给一段需求 / 一张截图 / 一份 Figma 链接,它能产出 Flex / Grid 方案, 准确率 80% 以上。所以"全部 review、逐行检查 AI 写的 CSS" 既不现实 也不必要——那是把 AI 当人用,不是把人当人用。
那人到底该看什么?
10.1 三件事:人必看的最小集
按"漏掉的代价"从高到低排:
| # | 看什么 | 为什么 AI 容易漏 | 怎么看(30 秒内) |
|---|---|---|---|
| 1 | 选对了 Flex / Grid / 容器查询吗 | AI 默认按"最像见过的代码"选,不知道你这个组件未来会被嵌到哪里 | 看 §1 决策树;如果选反了,3 个月后要重写 |
| 2 | a11y / 语义 HTML 是否正确 | AI 会用 <div onclick> 替代 <button>,会漏 aria-label / 焦点顺序 | 看 §3.2 语义化 HTML 与可访问性;不对的话法律 / SEO 双重风险 |
| 3 | 响应式断点 + 边界情况是否考虑了 | AI 给的是"普适值",不知道你的产品 P95 用户用什么屏幕;窄屏 / 长文本 / 加载态全靠人 | 缩窗口拉到 320px / 1920px 各看一次 |
这三件事 AI 替代不了——它们都需要"业务 / 产品 / 真实用户"的 上下文,AI 不在场。其他的(属性写得对不对、单位用 px 还是 rem、 浏览器兼容性)AI 自己会查、会改,真出问题跑起来一眼就看出来。
这就是回到第 1 章讲的"工程师价值的新落点"在 CSS 这层的具体形态: 判断(选型)+ 上下文(业务)+ 验证(边界情况)。
10.2 不必看的:AI 写得比你稳的部分
坦白讲,下面这些不需要逐行看——浪费时间,跑起来不对再说:
- 属性名 / 取值 / 单位写法(AI 比多数人记得熟)
- Flex / Grid 内部的具体
align-items/gap值(视觉差异肉眼可见) - transition / transform 等动画属性(一秒看出来)
- 大多数颜色 / 间距 / 字号微调(视觉对比 + 设计稿)
不要在这些上消耗精力。把时间留给上面的 3 件最小集 + 自己写 (一定要自己写)那部分关键交互逻辑。
10.3 那"基础不再重要"了吗?恰恰相反
新人最容易问的:"既然 AI 能写,我还需要学 Flex / Grid / cascade 吗?"——需要,原因有三:
- 判断的前提是理解:你要看出"选反了",必须知道两边各自适合 什么。读不懂 AI 写的代码 = 看不出对错 = 做不了 review
- AI 翻车时的兜底:AI 写出
position: absolute跑飞、堆叠 上下文翻车(§12.4)、margin 折叠造成空白时——你得能在 DevTools 里 5 分钟定位,否则就是把项目命运交给 AI - 品味是长期资产:看 100 段 AI 写的布局代码,你能记住"业界 多数人这样写",但记不住为什么这样写。3 年后换新框架 / 新 范式时,没有底层心智的人会被淘汰
设计判断(D5:长期委托会失去自保能力):把"看代码细节"全 交给 AI 没问题;把"判断 / 上下文 / 验证"全交给 AI 等于自我 阉割未来岗位。这是 §1.4 D5 红线在 CSS 这层的最直接体现。
10.4 推荐的协作模式
业务里实际能用的人 + AI 协作 7 步:
- 人:把需求拆成"布局需求 + 交互需求 + 边界情况"三段,写进 prompt
- AI:产出第一版 HTML + CSS
- 人 30 秒:扫上面 §10.1 三件事——选型对不对、a11y 漏没漏、 边界考虑了吗
- AI:根据上面三条提的修改
- 人 5 分钟:浏览器跑起来,缩窗口拉到 320px / 1920px / 看一次 暗色模式(如果有)
- AI:修边界 case
- 人最后:合并 + commit message 自己写(commit message 是 你自己思路的存档,AI 写不出原汁原味)
AI 写代码、人审 3 件事 + 验证边界——这才是 2026 年的现代协作 模式。把这套流程跑顺,你比"全部自己写"快 5 倍,比"全部 AI 写" 准 10 倍。
11. 布局之外的"隐藏头号杀手"
学完 Flex / Grid 仍会在业务里翻车——因为 CSS 真正的难点不在"怎么 写一个布局",而在周边那些"看起来无关"的机制突然冒出来咬你。 这一节给你一张地图,下面每条遇到了知道去哪查;展开都在 §3.3 现代 CSS 特性 / §3.6 动画与层级。
11.1 margin 折叠(margin collapsing)
症状:父子元素之间多了 / 少了空白;上下兄弟元素的 margin 没"加 起来",反而合并成大的那个。
原因:相邻块级元素的垂直 margin 会合并——这是 CSS 1 的 设计,初衷是让连续段落间距规整,但在组件化时代经常意外触发。
最常见的躲坑姿势:父元素加 display: flex / display: grid /
overflow: hidden 之一——让父元素成为一个"独立格式化上下文"
(这就是 BFC,下条),margin 折叠不会跨越它。
11.2 BFC(块级格式化上下文)
一句话:一个自成一体的布局区域,内部元素的 margin / float / 浮动清除等行为不影响外面,外面也不影响它。
触发它的常见声明:display: flex / display: grid /
overflow: hidden / position: absolute / display: flow-root
(最干净的现代触发器,专门为此设计)。
为什么要懂它:上面 margin 折叠的修复、"父元素塌陷"的修复、 "两栏布局右侧自适应"的修复,全都是"让某个元素变成 BFC"的应用。 理解一次,受用很久。
11.3 包含块(containing block)
症状:position: absolute 的元素跑到了意料之外的地方——
你以为它相对父元素,结果跑到了页面角落。
原因:absolute 元素的参照系("包含块")是最近的有定位的
祖先——没有就一路追到 viewport。父元素如果没设
position: relative(或其他非 static 值),就不会成为参照系。
修复:父元素加一句 position: relative。
11.4 堆叠上下文(stacking context)
症状:你给元素加了 z-index: 9999,结果还是被另一个 z-index
仅为 1 的元素盖住了。
原因:z-index 的比较只在同一个堆叠上下文内进行。一旦某个
祖先创建了独立的堆叠上下文(opacity < 1、transform 不为 none、
filter、isolation: isolate、will-change 等都会创建),里面
的 z-index 就被关进了那个上下文,再大也跨不出去。
修复:先用 DevTools 找出"被困住"的堆叠上下文边界(具体怎么用
看 §12.5 的 Layers 面板),再决定是把它打通(去掉触发条件)还是
把目标元素提升到顶层(用 isolation: isolate 显式控制)。
11.5 盒模型(box-sizing)
症状:你给元素设了 width: 200px; padding: 16px;,结果它实
际占了 232px——超出预期 32px。
原因:默认 box-sizing: content-box——width 只算内容区,
padding / border 都加在外面。
修复 + 现代基线:所有现代项目(包括本仓库)都会在 reset 里加
一句 *, *::before, *::after { box-sizing: border-box; }——把
width 改成"包含 padding 和 border"的总宽度。这是 2026 年新
项目的事实标准,遇到没设的老项目就是 reset 缺失的信号。
/* 现代项目第一行 reset */
*,
*::before,
*::after {
box-sizing: border-box;
}12. 用 DevTools 看穿布局:现代调试技巧
CSS 真正的学习曲线不在"读懂 MDN",而在"页面长得不对的时候你知道 往哪看"。这一节是这章里密度最高的实践片段——很多概念光读文字 怎么读都迷糊,但你在 DevTools 里亲手切一下、调一下就立刻明白了。
下面以 Chrome DevTools 为例,截图风格 / 名称都按 Chrome 134+ 的 现代界面。Firefox / Safari 等价功能都有,叫法略不同——参考 MDN: Firefox 开发者工具
- Safari Web Inspector。 原理类似,迁移成本很低。
12.1 必装:高亮 Grid / Flex / 容器查询
打开任意页面 → F12 / Cmd+Opt+I → Elements 面板:
- 选中一个
display: grid元素,旁边会出现一个grid徽章—— 点它,整个 Grid 的轨道线、行号、间隙、命名区域都覆盖在页面 上显示。改grid-template-columns时实时跟着变 display: flex元素同理,徽章是flex——开启后能看到主轴 方向、justify-content的分布点、align-items的对齐线container-type: inline-size元素有container徽章——开启 后能看到查询源边界
这些 overlay 是免费的"心智模型可视化"——你在表格里读 10 遍
"主轴交叉轴",不如开 flex overlay 自己改一行 flex-direction: column 看着主轴翻转。一手参考:
Chrome DevTools: Inspect CSS grid
12.2 实时改 CSS:右侧 Styles 面板
选中元素后 Styles 面板从上到下是它命中的所有规则:
- 直接点击属性值就能改——回车看效果,
Esc撤销 - 数字属性可以按
↑/↓调:1px一档;Shift+↑/↓是 10px /Alt+↑/↓是 0.1px——调间距 / 阴影偏移时极顺手 - 取消勾选某条规则测试"去掉它会怎样"——比注释代码快 10 倍
- 右上角
+加新规则,下方:hov可以强制元素处于:hover/:focus/:active状态——调 hover 样式不用真的 把鼠标按住
新人最容易忽视的功能:在 Styles 上方搜索框输入属性名(如
flex-direction)能快速过滤——大型设计系统层层覆盖时找元凶必备。
12.3 看真正生效的值:Computed 面板
Styles 显示"声明了什么",Computed 面板显示"浏览器最终算出 了什么"——单位都换成 px、百分比都解析出实际值、继承的属性都列 出来。
最有用的两个场景:
- "我设了
width: 100%但元素只有 240px"——Computed 看实际是多少; 再看下面的"由谁继承 / 哪条规则赢了",沿链回溯 - 排查继承——把 Computed 切到
Show all,所有从父元素继承下来 的字体 / 颜色 / 行高都列出来,每条都能点开看"是从哪个祖先来的"
12.4 调试样式没生效:cascade / specificity 实战
最高频问题:"我加了样式但没生效"。Styles 面板会用三种方式 告诉你原因:
- 属性被划线(strike-through):被另一条规则覆盖了;点过去就 跳到赢家规则
- 规则上方显示
Inherited from <selector>:当前生效是从父元 素继承下来的;要在子元素上覆盖,给子元素直接设 - 小三角警告图标:值无效(拼写错 / 单位错 / 不兼容当前 display)
DevTools 里右键属性 → Copy specificity(Chrome 实验功能, Settings → Experiments 里开启)能直接拿到当前规则的优先级数值 ——和"赢家规则"对比就知道差几票。
12.5 翻车排查神器:Issues / Recorder / Layers
- Issues 面板(
Cmd+Shift+P搜Issues):浏览器主动报告的 CSS / a11y / 安全问题,包括"这条规则被忽略了"+ 原因——出 bug 时必看 - Layers 面板(同样从 Command Menu 打开):当前页面的所有合成
层;调"为什么 z-index 9999 还是被盖"时打开看是不是被祖先
transform/filter切成了独立堆叠上下文(§11.4) - Rendering 面板:勾选 Layout shift regions 实时高亮 CLS、 Paint flashing 实时高亮重绘——回到第 2 章 §2.3 的渲染管线 在 DevTools 里"看见"
12.6 设计稿还原 / 视觉验收
- Cmd+Shift+P → Capture full size screenshot(整页截图)—— 和设计稿对比时不用滚动拼图
- Device Toolbar(
Cmd+Shift+M):切换到任意手机尺寸;可以 自定义"Custom" 加进自家设计标准断点(如360 / 768 / 1024 / 1440) - 选中元素时按住
Shift——能看到该元素到旁边元素的距离 (Figma-like ruler);调间距时极快 - Color Picker:点 Styles 里的色块小方块直接吸取页面其他位置 的颜色——做"和设计稿对比"必备
12.7 进阶工具(按需了解)
- Polypane(付费,免费试用):同时在 多个尺寸 + 暗色 + 截图模式里开发;做响应式时一屏覆盖全场景
- Pesticide :浏览器扩展 Pesticide 给所有元素加 outline 一眼看穿层次结构
- Stylify Me:抓取一个网站的色板 / 字体 / 间距,看别人家的设计令牌怎么组织
- CSSTriggers:每个 CSS 属性会触发 渲染管线的哪些阶段(layout / paint / composite)——查"这个属性 改了会不会很贵"必备
12.8 推荐的"实践式学习"流程
读完这一节,不要直接进下面的练习清单——先做这件事:
- 打开你最熟的中文网站(掘金 / 知乎 / B 站 / 淘宝 / 小红书 / 自家产品都行)
- 选 1-2 个有意思的布局元素,开 grid / flex overlay 看它怎么搭
- 在 Styles 里改一两个值看效果(
gap/flex-direction/grid-template-columns) - 关 DevTools 后自己在空白文档里重写一遍
为什么挑这几个站:B 站首页的视频卡片网格、知乎的回答列表、 掘金文章页的两栏布局、小红书的瀑布流、淘宝商品详情的复杂 Grid—— 都是中文产品里布局复杂度足够、又访问稳定的练习对象。比起对 着英文教程抄一遍 demo,拆解你每天在用的产品是怎么搭的,记忆深得多。
这个"看 → 改 → 重写"循环跑 5 个不同布局,比读 5 篇教程效果好得多—— 单纯阅读文字始终很难理解 CSS,必须围绕具体问题动手。
13. 经典布局案例:业务里反复出现的三个
下面三个案例覆盖业务里出现频率极高的 CSS 技巧——头像叠放 / 通知 徽章 / 骨架屏。每个 demo 都是真实运行的 React + 内联样式,下方 可展开源码(含 HTML 结构 + CSS)一键复制。建议自己改一两个值 看效果,比单纯读源码记忆深。
三个案例分别对应三类经典 CSS 模式:负 margin(让"叠放"成 为可能)、
position: absolute+ 包含块(徽章定位的根)、@keyframes+prefers-reduced-motion(动画的标准实现)。 看懂这三个,业务里 70% 的"小组件"都不用查文档了。
13.1 协同头像叠放(avatar stack)
GitHub PR / Vercel Deployment / Figma 评论里都有的"参与者头像叠 着显示"——核心是负 margin 让头像重叠 + 同色边框制造视觉 分隔线。
查看源码html
<div class="avatar-stack">
<span class="avatar">A</span>
<span class="avatar">B</span>
<span class="avatar">C</span>
<span class="avatar">D</span>
<span class="avatar">E</span>
</div>
<span class="more">+3</span>
<style>
.avatar-stack { display: flex; }
.avatar {
width: 32px;
height: 32px;
border-radius: 50%;
border: 2px solid var(--bg); /* 与背景同色,营造"留白"分隔 */
display: grid;
place-items: center;
color: white;
font-weight: 600;
}
.avatar:not(:first-child) {
margin-left: -10px; /* 关键:负 margin 让头像叠起来 */
}
</style>为什么这样写:
margin-left: -10px是关键——元素向左挪让相邻头像叠起来; 数值越大叠得越深:not(:first-child)确保第一张不被挪走(不然整组会偏移)border: 2px solid var(--bg)——边框色和容器背景同色,制造 "白色描边"分隔感;切到暗色 / 切到非白底容器都自动跟随- 想让后面的头像盖在前面之上?加
.avatar:hover { z-index: 1 }即可
变体:要"反向叠"(前面盖后面)→ 用 flex-direction: row-reverse
或给每个头像递减的 z-index。
13.2 通知徽章(notification badge)
红点 / 数字角标——任何带通知的图标按钮都用得到。核心是 position: relative 父 + position: absolute 子,徽章相对图标定位。
查看源码html
<button class="icon-btn" aria-label="通知(有新消息)">
🔔
<span class="dot" aria-hidden="true"></span>
</button>
<button class="icon-btn" aria-label="收件箱(12 条新消息)">
✉️
<span class="num-badge" aria-hidden="true">12</span>
</button>
<button class="icon-btn" aria-label="收件箱(99+ 条新消息)">
✉️
<span class="num-badge" aria-hidden="true">99+</span>
</button>
<style>
.icon-btn {
position: relative; /* 关键:作为徽章的包含块 */
width: 40px; height: 40px;
}
/* 红点:纯装饰,固定 10×10,简单 */
.dot {
position: absolute;
top: 6px; right: 6px;
width: 10px; height: 10px;
border-radius: 50%;
background: #ef4444;
border: 2px solid var(--bg); /* 留白让小红点边缘清晰 */
box-sizing: border-box; /* border 算入尺寸,不撑大 */
}
/* 数字角标:可变宽度("12" 圆,"99+" 长胶囊) */
.num-badge {
position: absolute;
top: -6px; right: -8px; /* 露出图标边缘 */
min-width: 20px; height: 20px; /* min-width 让短文本仍是圆 */
padding: 0 6px;
border-radius: 999px; /* 大值=任意宽都成胶囊 */
background: #ef4444;
color: white;
font-size: 11px;
line-height: 1;
border: 2px solid var(--bg);
box-sizing: border-box;
display: inline-flex;
align-items: center;
justify-content: center;
white-space: nowrap; /* "99+" 不被换行切成两行 */
}
</style>为什么这样写:
- 父按钮
position: relative是徽章的包含块——这正是 §11.3 的应用:忘了这一行徽章会跑到<body>角落 - 数字角标用
top: -6px; right: -8px露出图标边缘,min-width: 20px; height: 20px+border-radius: 999px让短文本("12")保持圆、 长文本("99+")自动撑成胶囊;box-sizing: border-box让 border 算入尺寸不撑大 border: 2px solid var(--bg)同样起描边作用——让小徽章在任何 背景上都不糊- a11y:图标按钮用
aria-label包含通知数;徽章本身aria-hidden="true"避免读屏重复
陷阱:用 right: 0 或 right: -100% 这种相对宽度的写法在
不同字号下会偏移——固定 px 偏移最稳。
13.3 骨架屏(loading skeleton)
接口返回前先显示"占位骨架"——比转圈圈友好得多。核心是线性渐变
背景 + background-position 动画。
查看源码html
<article class="card" aria-busy="true" aria-label="加载中">
<div class="skeleton" style="height: 140px;"></div>
<div class="skeleton" style="height: 14px; width: 70%;"></div>
<div class="skeleton" style="height: 12px;"></div>
<div class="skeleton" style="height: 12px; width: 85%;"></div>
</article>
<style>
/* 浅灰背景 + 略深的"亮带"扫过;亮 / 暗模式各给一组色值 */
:root {
--skeleton-base: #f3f4f6;
--skeleton-highlight: #e5e7eb;
}
@media (prefers-color-scheme: dark) {
:root {
--skeleton-base: #27272a;
--skeleton-highlight: #3f3f46;
}
}
.skeleton {
border-radius: 6px;
background: linear-gradient(
90deg,
var(--skeleton-base) 0%,
var(--skeleton-highlight) 50%,
var(--skeleton-base) 100%
);
background-size: 200% 100%;
animation: shimmer 1.4s ease-in-out infinite;
}
@keyframes shimmer {
0% { background-position: 200% 0; }
100% { background-position: -200% 0; }
}
/* 尊重用户 reduced-motion 偏好——闪烁动画对前庭功能障碍 / 偏头痛
用户刺眼。关键:要保留可见的占位(背景仍渐变),只停动画 */
@media (prefers-reduced-motion: reduce) {
.skeleton { animation: none; }
}
</style>为什么这样写:
linear-gradient(90deg, base, highlight, base)制造一道"亮带" 从左到右扫过——只用一个渐变就完成background-size: 200% 100%让背景比容器宽两倍,配合background-position从200%动到-200%实现"无限滑过"@keyframes命名为有语义的skeleton-shimmer而非pulse—— 方便后续 grep 定位prefers-reduced-motion: reduce必须配上——闪烁动画对前 庭功能障碍 / 偏头痛用户刺眼;尊重系统偏好是 a11y 基础(参 MDN: prefers-reduced-motion)- a11y:父容器
aria-busy="true"+aria-label="加载中",让读屏 正确播报"加载中"而不是把每个骨架块当独立元素读
变体:纯静态版(不要动画)只留渐变背景;快速版用 @keyframes pulse { 50% { opacity: 0.5 } } 配 transition: opacity 也能做。
14. 最小练习清单
读完这一节,照下面练一遍——能写出来才算掌握:
- 写一个导航栏:左 logo、中间链接平均分布、右 "登录"按钮
- 写一个卡片网格:每列至少 240px、间距 1rem、自动换行
- 写一个整页布局:header + sidebar(240px) + main + footer,最小高 100vh
- 把"卡片"组件做成容器查询响应:自身 ≥ 480px 时左右两栏,否则 上下堆叠
- 把一段
padding: 0 16px; margin: 8px 0改成逻辑属性
复杂面专项(每题对应 §11 的一条):
- 复现一次 margin 折叠:写两个相邻段落各设
margin-block: 16px, 量出实际间距,然后用display: flow-root让它失效 - 故意做一次"z-index 失效":父元素加
transform: translateZ(0), 子元素z-index: 9999仍盖不过外面z-index: 1的元素 - 写一个绝对定位弹窗,故意忘记给父元素
position: relative, 看它跑到哪里——再加上看它回归 - 在
box-sizing: content-box默认下设width: 200px; padding: 16px;,再切到border-box,量两次实际宽度
每题 ≤ 10 行 CSS。写不出来回到对应节再读一遍。
延伸阅读
- MDN: CSS Flexible Box Layout — Flex 的中文一手参考
- MDN: CSS 网格布局 — Grid 的中文一手参考
- MDN: CSS 容器查询 — 中文一手;含可运行示例
- CSS-Tricks: A Complete Guide to Flexbox (英文) + Grid 同系列 — 社区事实标准的 Flex / Grid 速查
- Every Layout (英文) — Heydon Pickering,把"常见布局"抽象成可复用 primitive;适合资深读者
- Smashing Magazine: A Complete Guide to CSS Container Queries (英文) — 容器查询的深入文章
访问性说明:MDN 中文版可直接访问;CSS-Tricks / Every Layout / Smashing 英文站偶有访问波动,但绝大多数 ISP 可达。
下一节:§3.2 语义化 HTML 与可访问性
——<button> 不是 <div onclick>。