为什么现代布局绕不开 Flex 与 Grid

十年前写页面,很多人的第一反应是 float 加清除浮动,再用负外边距和绝对定位去「挪」元素。这类方案能出效果,但代价是:布局意图被藏在补偿性的样式里,改一处、塌一片,接手的人不敢动。Flex 与 Grid 改变的正是这件事——它们把「怎么排」从技巧变成了语言。

Flex(弹性盒子,CSS Flexible Box Layout)解决的是一维排布:一排菜单、一列表单、一组按钮、一个底部操作栏。Grid(网格布局)解决的是二维排布:既有行又有列的页面骨架、卡片墙、仪表盘、后台管理的三栏布局。两者不是替代关系,而是分工关系——这一点想通了,后面的选择就不会纠结。

一句话记住它们的定位:Flex 管一条线,Grid 管一个面。当你需要「把一串东西按某个方向摆开并分配空间」时用 Flex;当你需要「先划好格子,再把内容放进指定格子」时用 Grid。

不要再用浮动去实现布局了。浮动是文字环绕的排版工具,弹性盒子与网格才是布局工具。把旧习惯换掉,CSS 代码量通常会减少一半。
本文核心知识点
  • 主轴与交叉轴Flex 的一切对齐都围绕这两条轴展开,方向由 flex-direction 决定。
  • flex 简写flex: grow shrink basis,理解 basis 才能预判元素宽度。
  • 网格轨道Grid 先定义列与行(track),再用网格线把内容放进去。
  • minmax 与 auto-fill一条声明就能实现不依赖媒体查询的响应式卡片墙。
CSS 布局示意:蓝色渐变背景上的二维网格骨架与沿网格排布的圆角卡片,虚线弹性连线牵引等距小方块
网格与弹性:一片是成行的轨道,一条是伸缩的连线——Flex 与 Grid 正是这两类秩序的实现方式。

Flex 主轴与交叉轴:一维排列的思维模型

给容器加上 display: flex 后,它的直接子元素会变成「弹性项目」,并沿一条主轴排列。主轴方向由 flex-direction 决定:row(默认,横向)或 column(纵向)。与主轴垂直的称为交叉轴。所有对齐属性都只作用在这两条轴上,这是 Flex 最容易被误解的地方。

关键映射关系是:justify-content 控制主轴方向的对齐与分配,align-items 控制交叉轴方向的单行对齐,align-content 只在换行后作用于多行整体。把这三个属性和两条轴对起来,就再也不会记错谁管横谁管竖。

属性作用常用值
flex-direction决定主轴方向,其余属性都随之改变含义row / column / row-reverse
justify-content主轴上的对齐与剩余空间分配flex-start / center / space-between / space-evenly
align-items交叉轴上的对齐方式stretch / center / flex-start
gap项目间距,不产生边缘多余空隙任意长度,如 12px / 1rem
flex-wrap主轴空间不足时是否换行nowrap / wrap

项目一侧还有三个属性值得单独记:flex-grow(空间富余时是否放大)、flex-shrink(空间不足时是否收缩)、flex-basis(初始尺寸)。它们的简写 flex 决定了「谁更大、谁更小」。日常最常用的两档是 flex: 1(可放大可收缩、基准为 0,用来瓜分剩余空间)与 flex: 0 0 auto(完全按内容尺寸,不参与分配)。

  • flex: 1 让所有这类项目等宽平分,是工具栏、分栏容器的主力写法。
  • flex: 0 0 auto 用来保护图标、头像、按钮不被压扁。
  • min-width: 0 是防溢出补丁:弹性项目默认最小尺寸为内容宽度,长英文单词会把容器撑破,加它即可让文本正常换行。

Flex 实战:导航、卡片与垂直居中

弹性盒子的价值在于把「老办法要写一堆辅助类」的事变成几行声明。最常见的三个场景是顶部导航两端对齐、卡片底部按钮分列两侧、以及弹窗垂直居中。下面这段 CSS 可以直接拿去用,也可以放进任意项目的公共样式里。

/* 顶部导航:两端对齐 */
.site-nav {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
}
.site-nav .links {
  display: flex;
  gap: 12px;
  flex: 1;
  min-width: 0;      /* 防长文本撑破容器 */
}

/* 万能垂直居中:三行搞定 */
.modal-wrap {
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 60vh;
}

垂直居中曾经是 CSS 的经典难题,现在它是 display: flex 加两行对齐声明的事。原因在于 justify-content: center 把主轴上的剩余空间平均分到两侧,align-items: center 在交叉轴上做同样的事,两个方向一起居中,效果就出来了。

几个容易踩的细节值得单独记住:

  • 间距优先用 gap,而不是给每个项目加 margin。margin 会出现在首尾元素外侧,需要 :last-child 特殊处理,gap 不会。
  • 需要换行时显式写 flex-wrap: wrap。默认不换行,项目会被压缩到变形。
  • 「最后一个元素靠右」用 margin-left: auto。给该项目加这一条,它会吃掉主轴上的所有剩余空间,比绝对定位干净得多。
  • 等高列免费得到。交叉轴默认 stretch,同一行的项目自动等高,这正是很多「卡片高度不一致」问题的解药。

Grid 网格线:二维布局的表达力

Grid 的思路与 Flex 完全不同:先在容器上划出轨道(行与列),再把子元素放进指定的格子。轨道之间的分界称为网格线,从 1 开始编号;因此 grid-column: 1 / 7 表示「从第 1 条列线跨到第 7 条列线」,也就是占 6 列。

定义轨道用 grid-template-columns 与 grid-template-rows,单位 fr 表示「剩余空间的一份」。与百分比不同,fr 会自动扣除 gap 再分配,不会因为间距而溢出;配合 minmax(0, 1fr) 还能防止长内容顶破轨道。

/* 一个 12 列、列间距 24px 的网格 */
.grid {
  display: grid;
  grid-template-columns: repeat(12, minmax(0, 1fr));
  gap: 24px;
}

/* 用网格线指定位置:从第 1 条列线跨到第 7 条 */
.main-col { grid-column: 1 / 7; }
.side-col { grid-column: 7 / 13; }

/* 命名区域,可读性最高 */
.layout {
  display: grid;
  grid-template-columns: 240px minmax(0, 1fr);
  grid-template-areas: "side main" "side foot";
  gap: 24px;
}
.layout .side { grid-area: side; }
.layout .main { grid-area: main; }
.layout .foot { grid-area: foot; }

三处值得一提的能力:其一,grid-template-areas 用字符串画出布局草图,可读性远高于一堆行列数字,适合页面级骨架;其二,没有显式定位的子元素会按 grid-auto-flow 自动填充,卡片墙就是靠它自动换行;其三,Grid 的 gap 同样不产生边缘空隙,这是它比 margin 方案更省心的原因之一。

Grid 实战:响应式卡片墙与页面骨架

「不写媒体查询也能自适应」是 Grid 最实用的特性,靠的是 repeat(auto-fill, minmax(240px, 1fr)):浏览器尽可能多地塞入宽度不小于 240px 的列,多余空间再平摊,容器变窄时列数自动减少。它把过去三四个断点的媒体查询压缩成一行。

/* 响应式卡片墙:无需媒体查询 */
.card-wall {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
  gap: 20px;
}

/* 卡片内部仍用 Flex 处理一行按钮 */
.card-foot {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
}

注意倒数第二行那行注释点出的组合关系:页面级用 Grid 划骨架,组件级用 Flex 排一行。这不是规定,而是让代码最省心的默认分工——外层关心区域划分,内层关心顺序与对齐,两套语义各管一段。

auto-fill 与 auto-fit 常被混用,差别在空轨道是否保留:auto-fill 保留空列,卡片数量少时仍按最小宽度排列、不会拉得过宽;auto-fit 折叠空轨道,让剩余卡片撑满整行。列表页希望保持卡片尺寸稳定,选 auto-fill;希望「一张卡片也铺满」时选 auto-fit。

  • 卡片墙用 auto-fill,保持视觉节奏一致。
  • 单列阅读型内容用 minmax(0, 720px) 加 justify-content: center 限宽居中。
  • 骨架布局用 grid-template-areas,媒体查询里只改面积定义即可切换为单列。

Flex 与 Grid 如何组合

把两者对立起来是常见误区。真实项目里它们几乎总是同时出现:一个后台管理页,外层 Grid 划分侧边栏与主区,主区内部 Grid 铺卡片墙,每张卡片的标题行用 Flex 两端对齐,表单字段用 Flex 纵向排列。它们不是二选一,而是各自解决自己最擅长的那一层。

维度FlexGrid
布局维度一维:主轴 + 交叉轴二维:行与列同时定义
尺寸来源由内容与剩余空间分配由轨道定义,可精确到网格线
对齐方式主轴一条、交叉轴一条每个格子可独立对齐
典型场景导航、工具栏、按钮组、垂直居中页面骨架、卡片墙、仪表盘、表单栅格
学习曲线概念少,上手快轨道与网格线稍抽象,表达力更强

决策上的经验规则是:当元素的排列顺序由内容决定、数量不固定时用 Flex;当布局由设计稿决定、需要行列对齐时用 Grid。一条导航里几个菜单项,数量可变、只排一排,用 Flex;一个「左侧栏 + 主区 + 底部」的固定骨架,行列明确,用 Grid。反过来用也能实现,但代码会别扭,维护成本更高。

还有一个容易被忽略的点:Grid 的子元素也可以是 Flex 容器,反之亦然,嵌套不受限制。真正需要克制的只有一件事——不要为了「看起来高级」而在一个区域里叠三四层网格,深嵌套会让响应式调整变得难以推理。