跳到主内容

3

现代布局

Flex、Grid、容器查询、子网格——99% 的页面布局靠这套

必学32 分钟

现代 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 被表现职责绑死、可访问性灾难、改一处全乱
~2003float + clear终于能用 CSS 控制布局父元素塌陷(要 clearfix hack)、垂直居中靠"魔法 margin"、做整页布局极痛苦
~2009display: inline-block不用 float 也能水平排列元素之间出现"幽灵空格"(HTML 里的换行被当空白);间距控制要靠 font-size: 0 这种 hack
~2017Flexbox(全支持)一维布局有了"主轴"心智二维场景仍要嵌套;最初没有 gap(后来补上了)
~2017Grid(全支持)二维布局首次原生可表达学习曲线略陡;嵌套对齐还得等 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 / minmaxFlex 的 flex-wrap 不能做"按容器宽度自动决定列数"
2整页 header / sidebar / main / footergrid-template-areas 的 ASCII 直观度 Flex 嵌套写法替代不了
3跨行 / 跨列对齐(订单详情、表格类视图)Flex 没有"行轨道"概念,多行无法跨项对齐
4子网格嵌套对齐(卡片标题 / 内容 / 底齐)subgrid 能让嵌套元素对齐到外层轨道,Flex 完全没有等价方案
5显式坐标定位(仪表盘、画布类布局)grid-column: 2 / 5 / grid-row: span 3 这种"放在第几格"

其他所有场景,Flex 真的够用——尤其是导航栏、按钮组、表单行、 弹窗内容、移动端列表这类一维布局,硬上 Grid 反而是炫技。

1.2 选型简化版

不想记 5 类场景?记 3 句话就够:

  1. 一维(一行或一列) → Flex
  2. 整页大布局 + 卡片网格自动列数 → Grid
  3. 以上都不是 → Flex 一把梭就行,不要纠结
场景推荐为什么不是另一个
导航栏(一行多个按钮)FlexGrid 也能做,但 Flex 的"主轴对齐"语义更贴近"一排东西"
卡片网格(多行多列)GridFlex 的换行需要算 flex-basis、间距控制变形;Grid 一行 repeat() 搞定
整页 header / sidebar / footerGrid + grid-template-areasFlex 嵌套地狱;Grid 命名区域读起来像 ASCII 画
表单一组 label + inputGrid(两列)多个 input 的右列会自然对齐;Flex 要给 label 加固定宽度
单个元素居中Flex 或 place-items: centerGrid 也行;这是最不重要的选型

1.3 老 Flex 用户的 5 分钟升级路径

如果你过去几年一直用 Flex 解决一切、最近没系统过 CSS,不必从头学 Grid——按下面的顺序补就够:

  1. 学 1 个 Grid 写法grid-template-columns: repeat(auto-fill, minmax(240px, 1fr))gap: 1rem——这一行是卡片网格的现代答案,记住这一行就解锁 80% 的 Grid 价值
  2. 学 1 个语法糖grid-template-areas——做整页布局时比嵌套 Flex 干净一倍,代码读起来像 ASCII 画
  3. 遇到上面 §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 的所有困惑都来自不分清"主轴"和"交叉轴":

ABC主轴 main axis →交叉轴 cross axis ↓flex-direction: row(默认)
Flex 容器里主轴是水平方向、交叉轴是垂直方向;改成 column 时两者交换
想做什么主轴属性交叉轴属性
元素在主轴上的分布justify-content
元素在交叉轴上对齐align-items
单个元素覆盖默认对齐align-self

记住这张图,Flex 的 90% 困惑就消失了。flex-direction: column 时主轴变成纵向,但属性名不变——这是新人最容易卡的点。

词源解读:为什么是 justifyalign,不是 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 的核心就两件事:

  1. 定义轨道(tracks):用 grid-template-columns / grid-template-rows 声明几行几列、每条轨道多宽
  2. 放置元素(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 到底是什么? frfraction 的 缩写,英语意思就是"分数 / 一份"。它不是固定长度单位(不像 px / rem / %),而是"剩余可分配空间的一份"。

关键在于"剩余"两个字。Grid 计算列宽时分两步:

  1. 先扣固定尺寸:所有 px / rem / % / auto / min-content 等"非 fr"轨道,按它们的需求先占据空间
  2. 再分剩余空间:把容器剩下的宽度,按 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

词源解读:fillfit 字面差什么? 英语本义就是这两个 关键字差异的最佳记忆法:

  • 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,互不相通。

❌ 没用 subgrid:每卡独立 Grid,body / footer 起始线不齐
短标题
body…
footer
一个相当长以至于会自然换到第二行的卡片标题
body…
footer
右卡标题占 2 行 → body 起始线被推下去;左卡 body 仍在第 2 行起。

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 落在同一根轨道上,跨卡片 天然对齐。

✅ 用 subgrid:两卡共享外层行轨道,body / footer 跨卡对齐
短标题
body…
footer
一个相当长以至于会自然换到第二行的卡片标题
body…
footer
左卡的 body / footer 被外层轨道下推到与右卡同一水平线上——这就 是 subgrid 让嵌套对齐"贯通"的效果。

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 给了 三个新单位:

单位全称行为
vhviewport height老单位;移动端会被工具栏遮挡,给"理论最大值"
svhsmall viewport height"工具栏全部展开时"的可视高度——最保守值
lvhlarge viewport height"工具栏全部收起时"的可视高度——最宽松值
dvhdynamic 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-leftmargin-inline-start
margin-rightmargin-inline-end
margin-topmargin-block-start
margin-bottommargin-block-end
widthinline-size
heightblock-size
padding-left/rightpadding-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 个月后要重写
2a11y / 语义 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 吗?"——需要,原因有三:

  1. 判断的前提是理解:你要看出"选反了",必须知道两边各自适合 什么。读不懂 AI 写的代码 = 看不出对错 = 做不了 review
  2. AI 翻车时的兜底:AI 写出 position: absolute 跑飞、堆叠 上下文翻车(§12.4)、margin 折叠造成空白时——你得能在 DevTools 里 5 分钟定位,否则就是把项目命运交给 AI
  3. 品味是长期资产:看 100 段 AI 写的布局代码,你能记住"业界 多数人这样写",但记不住为什么这样写。3 年后换新框架 / 新 范式时,没有底层心智的人会被淘汰

设计判断(D5:长期委托会失去自保能力):把"看代码细节"全 交给 AI 没问题;把"判断 / 上下文 / 验证"全交给 AI 等于自我 阉割未来岗位。这是 §1.4 D5 红线在 CSS 这层的最直接体现。

10.4 推荐的协作模式

业务里实际能用的人 + AI 协作 7 步

  1. :把需求拆成"布局需求 + 交互需求 + 边界情况"三段,写进 prompt
  2. AI:产出第一版 HTML + CSS
  3. 人 30 秒:扫上面 §10.1 三件事——选型对不对、a11y 漏没漏、 边界考虑了吗
  4. AI:根据上面三条提的修改
  5. 人 5 分钟:浏览器跑起来,缩窗口拉到 320px / 1920px / 看一次 暗色模式(如果有)
  6. AI:修边界 case
  7. 人最后:合并 + 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 < 1transform 不为 none、 filterisolation: isolatewill-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 开发者工具

12.1 必装:高亮 Grid / Flex / 容器查询

打开任意页面 → F12 / Cmd+Opt+IElements 面板:

  • 选中一个 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+PIssues):浏览器主动报告的 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 ToolbarCmd+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 推荐的"实践式学习"流程

读完这一节,不要直接进下面的练习清单——先做这件事:

  1. 打开你最熟的中文网站(掘金 / 知乎 / B 站 / 淘宝 / 小红书 / 自家产品都行)
  2. 选 1-2 个有意思的布局元素,开 grid / flex overlay 看它怎么搭
  3. 在 Styles 里改一两个值看效果(gap / flex-direction / grid-template-columns
  4. 关 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 让头像重叠 + 同色边框制造视觉 分隔线。

协同头像叠放GitHub / Vercel / Figma 风格
ABCDE
+3
查看源码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 子,徽章相对图标定位。

通知徽章红点 + 数字角标 + 99+ 三种形态
查看源码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: 0right: -100% 这种相对宽度的写法在 不同字号下会偏移——固定 px 偏移最稳。

13.3 骨架屏(loading skeleton)

接口返回前先显示"占位骨架"——比转圈圈友好得多。核心是线性渐变 背景 + background-position 动画

骨架屏渐变 shimmer 动画 + reduced-motion 适配
查看源码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-position200% 动到 -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-Tricks / Every Layout / Smashing 英文站偶有访问波动,但绝大多数 ISP 可达。


下一节:§3.2 语义化 HTML 与可访问性 ——<button> 不是 <div onclick>