现代 CSS 布局演进:从 Flexbox 到 Container Query
引言
CSS 布局在过去十年经历了从 float 到 Flexbox,再到 Grid,最终走向 Container Query 与 CSS Nesting 的革命。每一次演进不仅是 API 的增加,更是布局思维方式的转变。本文将系统性讲解每个特性的 API、浏览器支持、实战用法和最佳实践。
一、Flexbox 深入
1.1 Flexbox 基本模型
.container {
display: flex;
flex-direction: row; /* row | row-reverse | column | column-reverse */
justify-content: space-between; /* 主轴对齐 */
align-items: center; /* 交叉轴对齐 */
flex-wrap: wrap; /* 允许换行 */
gap: 16px; /* 间距,无需 margin hack */
}
.item {
flex: 1 1 200px; /* grow | shrink | basis */
order: 2; /* 视觉顺序,不影响 DOM */
align-self: flex-end;
}
1.2 flex 简写规则
.item {
flex: 1; /* = 1 1 0% → 弹性增长,可压缩 */
flex: auto; /* = 1 1 auto → 按内容大小,可增长可压缩 */
flex: none; /* = 0 0 auto → 固定大小,不可伸缩 */
flex: 2 1 300px; /* 增长权重 2,压缩权重 1,基准 300px */
}
陷阱:flex: 1 与 flex: 1 1 0% 相同,但与 flex: 1 1 auto 不同。前者基准为 0(按 grow 比例分配),后者基准为内容尺寸。
1.3 gap 属性与 flex-wrap
/* 响应式卡片网格 */
.cards {
display: flex;
flex-wrap: wrap;
gap: 1rem;
}
.card {
flex: 1 0 280px; /* 最小 280px,剩余空间均分 */
}
/* 视觉重排序:DOM 顺序不变,仅视觉调整 */
.card.featured {
order: -1; /* 置顶 */
}
/* 注意:order 仅影响视觉,Tab 仍按 DOM 顺序 */
1.4 浏览器支持
| 特性 |
Chrome |
Firefox |
Safari |
兼容性 |
| Flexbox 基础 |
全支持 |
全支持 |
全支持 |
✅ 99%+ |
gap |
84+ |
63+ |
14.1+ |
✅ |
二、Grid 布局
2.1 基础网格
.grid {
display: grid;
grid-template-columns: repeat(12, 1fr);
grid-template-rows: auto 1fr auto;
gap: 1rem;
}
.header { grid-column: 1 / -1; } /* 全宽 */
.sidebar { grid-column: 1 / 4; } /* 占 3 列 */
.main { grid-column: 4 / 13; }
.footer { grid-column: 1 / -1; }
2.2 Grid Areas 命名布局
.layout {
display: grid;
grid-template-columns: 250px 1fr;
grid-template-rows: auto 1fr auto;
grid-template-areas:
"header header"
"sidebar main"
"footer footer";
gap: 20px;
min-height: 100vh;
}
.header { grid-area: header; }
.sidebar { grid-area: sidebar; }
.main { grid-area: main; }
.footer { grid-area: footer; }
@media (max-width: 768px) {
.layout {
grid-template-columns: 1fr;
grid-template-areas:
"header"
"main"
"sidebar"
"footer";
}
}
2.3 auto-fit vs auto-fill
/* auto-fit:自动适应,空轨道折叠为 0 */
.grid { grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); }
/* auto-fill:保留空轨道 */
.grid-fill { grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); }
| 模式 |
子项数 < 列数 |
行为 |
auto-fit |
是 |
空轨道 collapse,子项被拉伸 |
auto-fill |
是 |
留下空轨道,子项保持原大小 |
2.4 subgrid(关键新特性)
subgrid 让嵌套网格继承父网格的轨道定义:
.card-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 1rem;
}
.card {
display: grid;
grid-template-columns: subgrid;
grid-template-rows: subgrid;
grid-row: span 3;
}
subgrid 解决了多卡片对齐内部元素的痛点。在没有 subgrid 之前,要实现一组卡片中标题/价格/按钮按行对齐,只能 JS 测量或使用 hacks。
| 特性 |
Chrome |
Firefox |
Safari |
状态 |
| Grid 基础 |
57+ |
52+ |
10.1+ |
✅ |
subgrid |
117+ |
71+ |
16+ |
✅ 2023+ |
三、Container Query
3.1 核心概念
传统 @media 是基于视口查询,而 @container 基于父容器查询。这让组件能根据自身容器宽度自适应,与视口无关。
.card-container {
container-type: inline-size;
/* 或简写: */
container: card-container / inline-size;
}
@container card-container (min-width: 400px) {
.card {
display: grid;
grid-template-columns: 120px 1fr;
gap: 1rem;
}
}
@container card-container (max-width: 399px) {
.card { display: flex; flex-direction: column; }
}
3.2 与 @media 的对比
| 维度 |
@media |
@container |
| 查询对象 |
视口 |
父容器 |
| 组件复用 |
难 |
容易 |
| 嵌套场景 |
困难 |
自然 |
| 性能 |
全局 reflow |
局部 reflow |
| 兼容性 |
全支持 |
Chrome 105+/Firefox 110+/Safari 16+ |
3.3 实战:响应式卡片
.product {
container: product / inline-size;
}
.product__inner {
display: flex;
flex-direction: column;
}
/* 300px+ 改为横向 */
@container product (width > 300px) {
.product__inner {
flex-direction: row;
gap: 1rem;
}
.product__image { width: 40%; }
}
/* 500px+ 显示更多详情 */
@container product (width > 500px) {
.product__extra { display: block; }
}
@container product (width <= 500px) {
.product__extra { display: none; }
}
四、CSS Nesting
4.1 原生嵌套
.card {
padding: 1rem;
background: white;
& .title {
font-size: 1.5rem;
font-weight: bold;
}
&:hover {
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
}
& > button {
background: blue;
}
@media (max-width: 600px) {
padding: 0.5rem;
}
}
| 特性 |
Chrome |
Firefox |
Safari |
| Nesting 基础 |
112+ |
117+ |
16.5+ |
五、CSS Layers
5.1 解决选择器权重战争
@layer reset, base, components, utilities;
@layer reset {
* { margin: 0; padding: 0; box-sizing: border-box; }
}
@layer base {
body { font-family: system-ui; line-height: 1.6; }
a { color: inherit; }
}
@layer components {
.btn { padding: 0.5rem 1rem; border-radius: 4px; }
.btn-primary { background: blue; color: white; }
}
@layer utilities {
.text-center { text-align: center; }
.mt-4 { margin-top: 1rem; }
}
5.2 优先级规则
- 同名 layer 后定义的覆盖前面的。
- 上层 layer 永远胜过下层,无论选择器权重。
- 未声明 layer 的样式(unlayered)优先于 layered 样式,作为兜底。
5.3 第三方库隔离
@layer reset, bootstrap, components, utilities;
@import url('bootstrap.css') layer(bootstrap);
@layer components {
.btn { border-radius: 0; } /* 自动覆盖 Bootstrap */
}
六、逻辑属性
6.1 物理属性 vs 逻辑属性
/* 物理属性:固定方向 */
.box-physical {
margin-left: 1rem;
margin-right: 1rem;
padding-top: 0.5rem;
}
/* 逻辑属性:基于书写方向 */
.box-logical {
margin-inline: 1rem; /* 替代 margin-left/right */
padding-block-start: 0.5rem; /* 替代 padding-top */
text-align: start;
}
6.2 对照表
| 物理属性 |
逻辑属性 |
说明 |
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 |
块尺寸 |
/* 自动适配 RTL */
.page {
padding-inline: 2rem;
margin-block: 1rem;
text-align: start;
}
/* html { direction: rtl; } 切换后自动适配 */
七、Scroll-Driven Animations
7.1 animation-timeline
@keyframes fade-in {
from { opacity: 0; transform: translateY(20px); }
to { opacity: 1; transform: translateY(0); }
}
.scroll-fade {
animation: fade-in linear;
animation-timeline: view();
animation-range: entry 0% cover 30%;
}
7.2 三种 timeline
| timeline |
触发条件 |
用途 |
scroll() |
页面/容器滚动进度 |
阅读进度条、视差 |
view() |
元素进入/离开视口 |
入场动画 |
| 自定义 |
JS ScrollTimeline |
复杂交互 |
7.3 实战:阅读进度条
.progress-bar {
position: fixed;
top: 0;
left: 0;
height: 4px;
background: linear-gradient(90deg, blue, purple);
width: 100%;
transform-origin: 0 0;
animation: grow linear;
animation-timeline: scroll(root);
}
@keyframes grow {
from { transform: scaleX(0); }
to { transform: scaleX(1); }
}
7.4 浏览器支持与降级
| 浏览器 |
版本 |
| Chrome |
115+ ✅ |
| Edge |
115+ ✅ |
| Firefox |
部分支持 ⚠️ |
| Safari |
TP only ⚠️ |
@supports (animation-timeline: view()) {
.section {
animation: reveal linear;
animation-timeline: view();
animation-range: entry 0% cover 40%;
}
}
@supports not (animation-timeline: view()) {
.section.is-visible {
opacity: 1;
transform: none;
transition: all 0.6s;
}
}
八、综合实战
@layer reset, base, components, utilities;
@layer components {
.product-grid {
container: products / inline-size;
display: grid;
grid-template-columns: repeat(auto-fit, minmax(min(100%, 280px), 1fr));
gap: 1.5rem;
}
.product {
display: grid;
grid-template-rows: subgrid;
grid-row: span 4;
&__image {
inline-size: 100%;
aspect-ratio: 4 / 3;
object-fit: cover;
}
@container products (inline-size > 600px) {
& {
grid-template-columns: 120px 1fr;
grid-template-rows: subgrid;
}
&__image { grid-row: span 4; }
}
&:hover {
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.12);
transform: translateY(-2px);
transition: all 0.2s ease;
}
@supports (animation-timeline: view()) {
animation: reveal linear;
animation-timeline: view();
animation-range: entry 0% cover 25%;
}
}
}
九、特性成熟度总览(2026 年)
| 特性 |
推荐使用 |
降级方案 |
| Flexbox |
✅ 全场景 |
- |
| Grid |
✅ 全场景 |
- |
gap |
✅ |
- |
subgrid |
✅ 已普及 |
JS 测量 |
| Container Query |
✅ 已普及 |
@media |
| CSS Nesting |
✅ |
Sass |
@layer |
✅ |
BEM 命名 |
| 逻辑属性 |
✅ 推荐 |
物理属性 |
| Scroll-Driven Animations |
⚠️ 部分 |
IntersectionObserver |
十、总结
现代 CSS 布局演进的核心趋势:
- 从全局到局部:
@media → @container,组件不再是视口的奴隶。
- 从命令到声明:JS 动画 → Scroll-Driven Animations,性能更好。
- 从物理到逻辑:物理属性 → 逻辑属性,天生 i18n 友好。
- 从冲突到分层:BEM 命名 →
@layer,权重战争终结。
- 从工具到原生:Sass → 原生 Nesting,构建依赖减少。
Comments | 0条评论