【每日一面】Flex 与 Grid 布局

2026-09-29 19 min 6530 字 -- 次阅读
摘要

Flex 一维排内容、Grid 二维搭骨架,分清内容驱动与布局驱动,讲清空间分配算法和 min-width: auto 陷阱,这道送分题才能答出区分度。

你平时写页面,Flex 和 Grid 是怎么选的?面试官端起咖啡,抛出这句看似闲聊的问题。它没有标准答案,却有一个参考的标准层次,答出两个都能用是及格线,答出一维与二维、内容驱动与布局驱动才算入门,能讲清空间分配算法和 min-width: auto 陷阱,才真正进入面试官的视野。

基础问答

问:一句话说清 Flexbox 和 Grid 的区别。

答:Flexbox 是一维布局,一次只处理一个方向(一行或一列);Grid 是二维布局,同时控制行与列。更深一层的区别是 Flex 是内容驱动,先有内容、再分空间;Grid 是布局驱动,先画格子、再放内容。这决定了两者几乎所有行为的差异。

问:Flex 的主轴和交叉轴由什么决定?justify-content 和 align-items 分别管哪个?

答:由 flex-direction 决定。row(默认)主轴水平、交叉轴垂直;column 两者互换。justify-content 管主轴对齐,align-items 管交叉轴对齐。所以经典的水平垂直双居中只要三行:display: flex; justify-content: center; align-items: center;。

问:flex: 1 是什么的缩写?grow、shrink、basis 各自控制什么?

答:flex: 1 等价于 flex: 1 1 0%。flex-basis 是分配空间前项目的主轴基准尺寸,优先级高于 width;flex-grow 定义有剩余空间时项目按多大权重瓜分增量;flex-shrink 定义空间不足时项目按多大权重交出尺寸。常用简写值还有 flex: auto(1 1 auto,基于内容伸缩)、flex: none(0 0 auto,完全刚性)。

问:Grid 中 fr、minmax()、repeat() 分别解决什么问题?

答:fr 是份数单位,grid-template-columns: 1fr 2fr 把可用空间按 1:2 切分;minmax(min, max) 给网格轨道划定上下限,防止内容把轨道挤爆或拉得过长;repeat() 负责批量生成轨道,repeat(3, 1fr) 等价于 1fr 1fr 1fr。三者组合出的 repeat(auto-fill, minmax(240px, 1fr)) 是不写媒体查询实现响应式布局的标准答案。

问:什么是显式网格与隐式网格?

答:由 grid-template-columns/rows 显式声明的轨道是显式网格;当项目数量超出显式轨道、或行轨道未定义时,浏览器自动补出来的轨道是隐式网格,尺寸由 grid-auto-rows/grid-auto-columns 决定,排列方向由 grid-auto-flow 决定。典型用法:只定义列轨道,行交给 grid-auto-rows: minmax(120px, auto) 自动生长。

问:实际项目里怎么选型?

答:有一个口诀,组件内部用 Flex,页面骨架用 Grid。导航栏、工具条、按钮组、单行内容流用 Flex;整体页面框架、后台仪表盘、卡片矩阵、行列严格对齐的版式用 Grid。两者不是二选一,现代页面通常是 Grid 划分区块、Flex 在区块内排内容。

扩展延伸

CSS 布局从 hack 走向系统

在 Flex 和 Grid 之前,CSS 其实没有真正的布局系统。float 生来是为文字环绕图片设计的排版属性,却被硬生生拿来搭页面,于是有了 clearfix、双飞翼、圣杯布局这些补丁式的工程智慧。这条演进路线值得花一些时间来记住:

timeline
    title CSS 布局能力演进
    1996 : CSS1:正常流与 display 初定
    1998 : CSS2:float 与定位撑起 hack 时代
    2012 : Flexbox 定稿:一维弹性与对齐能力
    2017 : Grid 全面落地:CSS 首个二维布局系统
    2020 : gap、subgrid 等补全:现代布局范式成型

2012 年 Flexbox 定稿,第一次把对齐和空间分配做成了系统能力。

2017 年 Grid 在 Chrome 57、Firefox 52、Safari 10.1 相继落地,CSS 才拥有第一个专为布局设计的模块。

核心机制

Flex:一条轴上的空间分配算法

Flex 布局的本质是先量内容,再分空间:

flowchart TD
    A["每个项目按 flex-basis 取基准尺寸"] --> B["auto 时回退到 width,再回退到内容尺寸"]
    B --> C{"基准尺寸之和 对比 容器主轴尺寸"}
    C -->|"有剩余空间"| D["按 flex-grow 加权瓜分<br/>增量 = 剩余 × grow ÷ Σgrow"]
    C -->|"空间不足"| E["按 flex-shrink 加权收缩<br/>缩减 = 溢出 × shrink × basis ÷ Σ(shrink × basis)"]
    D --> F["套用 min 与 max 约束"]
    E --> F
    F --> G["输出最终主轴尺寸"]

这里有两个容易被忽略的细节:

  • 收缩权重乘了 flex-basis:基准尺寸越大的项目,收缩时贡献越多,这是防止小项目被压没的保护机制;

  • flex 项目的 min-width 默认值是 auto(自动最小尺寸),项目不会被压缩到小于内容的 min-content,这正是大量的 flex: 1 不等分 bug 的根源。

Grid:一张画布上的网格系统

Grid 的心智模型是先画格子,再入座,下面这四个概念构成了全部的骨架:

  • 轨道(track):行与列的基本单位,尺寸可用 px/%/fr/minmax()/auto;

  • 网格线(line):轨道间的分隔线,从 1 开始编号,负数从末尾倒数,grid-column: 1 / 3 表示从第 1 根线跨到第 3 根线;

  • 网格区域(area):可命名的矩形范围,配合 grid-template-areas 用字符画直观定义整页结构;

  • 自动放置(auto-placement):未显式定位的项目按 grid-auto-flow(row/column/dense)的规则自动落格。

选型决策

在实际的需求中,选型其实只取决于一个问题:

flowchart TD
    A["开始布局设计"] --> B{"需要同时控制行和列吗"}
    B -->|"是"| C["Grid 二维网格"]
    B -->|"否"| D["Flex 一维流"]
    C --> C1["页面骨架、仪表盘、卡片矩阵<br/>先画格子再放内容"]
    D --> D1["导航、工具条、居中、单行单列<br/>内容驱动按轴对齐"]
    C1 --> E["实战组合:外层 Grid 搭骨架<br/>内层 Flex 排内容"]
    D1 --> E

代码示例

示例 1:Flex 处理一条线上的问题。

css
/* 水平垂直双居中:三行 */
.dialog {
  display: flex;
  justify-content: center; /* 主轴(水平)居中 */
  align-items: center;     /* 交叉轴(垂直)居中 */
}

/* 导航栏:logo 靠左,菜单靠右 */
.navbar {
  display: flex;
  align-items: center;
  gap: 16px;
}
.navbar .spacer {
  margin-left: auto; /* auto margin 吃掉全部剩余空间,把后续元素推到最右 */
}

示例 2:flex: 1 的 min-width 陷阱与修复。

css
.tabs {
  display: flex;
  width: 600px;
}
.tab {
  flex: 1;              /* 期望三等分 */
  min-width: 0;         /* 关键一行:覆盖 min-width: auto */
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

某个 tab 文案很长时,min-width: auto 会让它顶破等分。解法是显式声明 min-width: 0,或把 overflow 设为非 visible(自动最小尺寸随之失效)。

示例 3:Grid 圣杯布局,响应式只改字符画。

css
.layout {
  display: grid;
  min-height: 100vh;
  gap: 12px;
  grid-template-columns: 220px 1fr;
  grid-template-rows: auto 1fr auto;
  grid-template-areas:
    "header header"
    "side   main"
    "footer footer";
}
.layout > header { grid-area: header; }
.layout > aside  { grid-area: side; }
.layout > main   { grid-area: main; }
.layout > footer { grid-area: footer; }

@media (max-width: 768px) {
  .layout {
    grid-template-columns: 1fr;
    grid-template-areas:
      "header"
      "main"
      "side"
      "footer";
  }
}

HTML 结构完全零改动,窄屏下侧栏自动落到主内容之后。这是 grid-template-areas 相对定位写法的核心优势,即,布局设计清晰可读。

示例 4:不使用媒体查询的响应式卡片墙。

css
.card-wall {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
  gap: 16px;
}

每列下限 240px,能塞几列塞几列,剩余宽度由 1fr 均摊。从 240px 手机到 2K 显示器,一行 CSS 全覆盖。

示例 5:组合使用,外层 Grid 控面,内层 Flex 控线。

css
.card {
  display: flex;
  flex-direction: column;
  gap: 8px;
}
.card .desc {
  flex: 1;      /* 描述区吃掉剩余高度,按钮组永远贴底 */
  min-width: 0;
}
.card .actions {
  display: flex;
  justify-content: space-between;
}

对比

对比维度FlexboxGrid
布局维度一维(主轴方向)二维(行与列)
设计哲学内容驱动,先有内容再分空间布局驱动,先画格子再放内容
核心属性flex-direction、justify-content、align-items、flexgrid-template-*、grid-auto-flow、place-items
尺寸分配flex-grow 瓜分剩余、flex-shrink 收缩欠账fr 按份数切分、minmax() 定上下限
项目定位沿轴顺序排列,order 可调序按网格线或区域名精确定位,可跨行跨列
对齐粒度整行或整列行、列、单元格、命名区域均可独立对齐
间距gapgap(更早支持)
典型场景导航、工具条、居中、等分、单行单列流页面骨架、后台布局、卡片矩阵、杂志式排版
心智模型分蛋糕:一条轴上按权重切空间画格子:二维坐标里按位置放元素

简单来说,就是 Flex 排内容,Grid 搭骨架,gap 统一接管间距,margin hack 退役。

面试追问

追问1:flex: 1 展开是什么?为什么有时设置了 flex: 1,子项还是不等分?

答:flex: 1 是 flex-grow: 1; flex-shrink: 1; flex-basis: 0% 的缩写。不等分的元凶是 min-width 的默认值 auto,flex 项目的自动最小尺寸不会小于内容的 min-content,一个不换行的长单词、一张过宽的图片都能把项目顶开。修复方式:min-width: 0 或 overflow: hidden。同类问题还有flex 子项内 text-overflow: ellipsis 不生效,缺的也是这一行。

追问2:

xml
<div class="box">
  <div class="a">A</div>
  <div class="b">B</div>
  <div class="c">C</div>
</div>
css
.box { display: flex; width: 600px; }
.a { flex: 2 0 100px; }
.b { flex: 1 0 100px; }
.c { flex: 0 0 300px; }

三个 div 的最终宽度各是多少?写出计算过程。

答:基准尺寸合计 100 + 100 + 300 = 500px,剩余空间 600 − 500 = 100px;flex-grow 按 2:1 分配,A 得 100 × 2/3 ≈ 66.67px,B 得 100 × 1/3 ≈ 33.33px,C 的 grow 为 0 不参与。最终 A ≈ 166.67px,B ≈ 133.33px,C = 300px。

加分项:若三者 flex-basis 都改为 0%(即 flex: 2、flex: 1、flex: 0),基准合计为 0,600px 全部按 grow 权重分配,A = 400px、B = 200px、C = 0px,先把基准抹平为 0,再按权重切全部空间,这正是 flex: 1 能真等分的数学本质。

追问3:产品要求一个响应式卡片列表,手机一列、平板两列、桌面三到四列,并且不允许写媒体查询。怎么实现?

答:grid-template-columns: repeat(auto-fill, minmax(240px, 1fr))。minmax(240px, 1fr) 保证每列不窄于 240px 且均摊剩余宽度,auto-fill 让浏览器按容器宽度自动计算列数:320px 手机一列、768px 平板两列、1200px 以上四列。若追问 auto-fill 与 auto-fit 的区别,可以回答,项目少时,auto-fill 保留空轨道占位,auto-fit 把空轨道折叠为零、让现有项目拉伸铺满,这里的两个词语的差别,对应保持列宽稳定与卡片永远铺满容器两种诉求。

追问4:grid-auto-flow: dense 是干什么的?有什么隐患?

答:默认的自动放置是稀疏模式,放置光标只前进不回退,尺寸不一的项目会留下空洞。dense 开启回填后,任何项目只要塞得进之前的空洞就会被放进去,照片墙、标签墙场景能明显减少空缺。隐患在于视觉顺序与 DOM 顺序脱钩,像屏幕阅读器朗读、Tab 键焦点、:nth-child 选择器全都按 DOM 顺序工作,用户看到 A 在 B 前,焦点却可能先跳到 B。Flex 的 order 属性同理,能改视觉顺序,改不了文档顺序。表单、列表这类无障碍敏感的场景要慎用。

追问:Grid 功能这么全,Flexbox 会被淘汰吗?项目中两者怎么配合?

答:不会,两者是互补而非替代。Flex 解决一维、内容驱动的排布,擅长组件内部的图标、文字、按钮对齐;Grid 解决二维、布局驱动的骨架,擅长页面与区块的结构划分。典型实践中,页面级用 Grid 划出 header、side、main、footer,组件内部用 Flex 做行内弹性;Grid 管宏观对齐,Flex 管微观伸缩。加分方向:subgrid 让子网格继承父网格轨道,解决多卡片内多行文本底部对齐这类嵌套对齐难题,主流浏览器已全面支持;gap 已是 Flex 与 Grid 通用的间距方案,配合 grid-template-rows: auto 1fr auto 与 min-height: 100vh 可稳定搭出整页骨架。

写在最后

一维看轴向,二维看格子:Flex 排内容,Grid 搭骨架。面试时把这句话展开成维度、哲学、算法、场景四个层次,一道常见的送分题就能答出区分度。

评论