WSの小屋

现代 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: 1flex: 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 布局演进的核心趋势:

  1. 从全局到局部@media@container,组件不再是视口的奴隶。
  2. 从命令到声明:JS 动画 → Scroll-Driven Animations,性能更好。
  3. 从物理到逻辑:物理属性 → 逻辑属性,天生 i18n 友好。
  4. 从冲突到分层:BEM 命名 → @layer,权重战争终结。
  5. 从工具到原生:Sass → 原生 Nesting,构建依赖减少。

Comments | 0条评论