【每日一面】Flex 与 Grid 布局
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 处理一条线上的问题。
/* 水平垂直双居中:三行 */
.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 陷阱与修复。
.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 圣杯布局,响应式只改字符画。
.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:不使用媒体查询的响应式卡片墙。
.card-wall {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
gap: 16px;
}每列下限 240px,能塞几列塞几列,剩余宽度由 1fr 均摊。从 240px 手机到 2K 显示器,一行 CSS 全覆盖。
示例 5:组合使用,外层 Grid 控面,内层 Flex 控线。
.card {
display: flex;
flex-direction: column;
gap: 8px;
}
.card .desc {
flex: 1; /* 描述区吃掉剩余高度,按钮组永远贴底 */
min-width: 0;
}
.card .actions {
display: flex;
justify-content: space-between;
}对比
| 对比维度 | Flexbox | Grid |
|---|---|---|
| 布局维度 | 一维(主轴方向) | 二维(行与列) |
| 设计哲学 | 内容驱动,先有内容再分空间 | 布局驱动,先画格子再放内容 |
| 核心属性 | flex-direction、justify-content、align-items、flex | grid-template-*、grid-auto-flow、place-items |
| 尺寸分配 | flex-grow 瓜分剩余、flex-shrink 收缩欠账 | fr 按份数切分、minmax() 定上下限 |
| 项目定位 | 沿轴顺序排列,order 可调序 | 按网格线或区域名精确定位,可跨行跨列 |
| 对齐粒度 | 整行或整列 | 行、列、单元格、命名区域均可独立对齐 |
| 间距 | gap | gap(更早支持) |
| 典型场景 | 导航、工具条、居中、等分、单行单列流 | 页面骨架、后台布局、卡片矩阵、杂志式排版 |
| 心智模型 | 分蛋糕:一条轴上按权重切空间 | 画格子:二维坐标里按位置放元素 |
简单来说,就是 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:
<div class="box">
<div class="a">A</div>
<div class="b">B</div>
<div class="c">C</div>
</div>.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 搭骨架。面试时把这句话展开成维度、哲学、算法、场景四个层次,一道常见的送分题就能答出区分度。