CSS Flexbox:弹性布局入门 · Lesson 4 of 5

第 4 课:剩余空间的分配

本课目标:

  • 解释 flex-basisflex-growflex-shrink 各自控制什么。
  • 预测 flex-grow 如何划分自由空间(不是总宽度)。
  • 读懂 flex 简写,并知道 flex: 1 展开成什么。

先修条件:第 1-3 课(两条轴、justify-contentalign-itemsflex-direction) | 上一篇 << 03 | 下一篇 05 >>

“flex: 2 应该是两倍宽”,以及为什么通常不是

早晚有一天,你会给一个 item 写 flex: 1、给另一个写 flex: 2,然后期待第二个正好是两倍宽。有时看起来对,更多时候不对,而且你说不出为什么。原因是这些属性划分的不是 item 的宽度,而是 item 尚未占用的剩余空间。漏掉“剩余”这一个词,你设的每个比例都显得随机。这一课把“到底在分什么”钉死。

讲解

有三个属性控制 flex item 沿主轴的尺寸。一个一个来。

flex-basis 是 item 在任何自由空间分配之前的起始尺寸1。把它理解成“进入谈判之前,我想要多大”。默认值 auto 下,起始尺寸来自 item 自己的宽度或内容。

flex-grow 是一个增长因子:当有多余空间时,它决定 item 拿走多少正自由空间1。机制是比例,不是宽度。浏览器把每个 item 的增长因子加总,用总数去除自由空间,再把各自的份额分给每个 item1。所以 flex-grow: 2 挨着 flex-grow: 1,意思是第一个 item 拿到自由空间的三分之二,不是容器的三分之二。

flex-shrink 是镜像:当 item 总尺寸超出、出现自由空间需要扣除时的收缩因子1。收缩因子越高,意思是“超支时让出更多宽度”。有一个让收缩显得难以预测的细节:扣除量还会按每个 item 的基础尺寸加权,所以在收缩因子相同的情况下,宽的 item 比窄的 item 让出更多宽度1

“自由空间”这个词就是整课的核心。自由空间是把每个 item 的基础尺寸都预留之后剩下的部分。grow 和 shrink 永远只作用在这块剩余上。

这三个属性你很少分开设。flex 简写把它们合在一起:flex: 2 1 auto 表示 flex-grow: 2flex-shrink: 1flex-basis: auto,而且指南推荐用简写代替单独的属性2。把 flex 简写从左到右读成“grow、shrink、basis”,它就不再是一串魔法数字。常见的一行 flex: 1 展开为 flex: 1 1 0:grow 1、shrink 1、basis 0。因为 basis 是 0,每个 item 都从零起步,整条行都成了自由空间,所以给几个 item 都设 flex: 1 会让它们无视内容、真正等宽1

下面的导航栏有 logo、搜索框和按钮。现在搜索框只有占位文字那么宽。让它长开、填满中间的空白,同时 logo 和按钮保持自然尺寸。

只有搜索框拿到了增长因子,所以只有它认领自由空间。logo 和按钮的 grow 是 0,所以它们保持内容需要的大小不变。这就是日常的“一个弹性元素、几个固定元素”导航模式。

自由空间是有正负的,而且两个方向并不对称

“剩余”这个词藏了一个值得点名的区分。当容器比摆下这些 item 所需要的更大时,它有正自由空间——一个 500px 的行里装三个 100px 的 item,就有 200px 的正自由空间1。当 item 的自然尺寸加起来超过容器时,它有负自由空间:同样三个 item 每个 200px,合计 600px 塞进 500px 的行,就是 100px 的负自由空间1flex-grow 只作用于正的那种,flex-shrink 只作用于负的那种。如果你设了增长因子却什么都没发生,通常的原因就是根本没有正自由空间可分。

再看不对称。增长只按因子划分自由空间。收缩则要先把每个 item 的收缩因子乘上它的基础尺寸再分配,这样扣除量就按“各自还能让出多少”来摊——所以小 item 不会在大 item 明显变窄之前就被压到零1。除此之外,item 不会收缩到低于它的 min-content 尺寸,也就是仍能容纳最长那个词的最小尺寸1。这两条规则加在一起,既解释了为什么 flex-shrinkflex-grow 难预测,也解释了为什么收缩很少真的把内容毁掉。

item 起步用的那个尺寸自己也有一套词汇。min-content 大致是元素仍能装下最长单词的最小尺寸;max-content 是它要装下全部内容且不换行所需要的尺寸1。当 flex-basisauto 且 item 没有设 width 时,基础尺寸解析为该 item 的 max-content 尺寸1——这正是三张文字量不同的卡片起步宽度就不一样的原因。

读懂那几个预定义的 flex 值

除了 flex: 1,真实样式表里还常出现四种有名字的写法,每一个都只是三元组的伪装3

  • flex: initial 就是 0 1 auto——item 不会长过它的基础尺寸,但可以收缩以免溢出。这就是你什么都不写时已经得到的行为。
  • flex: auto 就是 1 1 auto——和 initial 一样,只是这个 item 还可以长开去填满容器。
  • flex: none 就是 0 0 auto——一个完全不可伸缩的 item:既不长也不缩,这才是“这个侧边栏就保持 240px”的正确说法。
  • flex: <数字> 就是 <数字> 1 0——basis 是 0 才是重点,这也是为什么 flex: 1 的那些 item 能无视内容、做到等宽3

还要注意 grow 和 shrink 的值是比例,不是单位。写 12 与写 1020、或者 .25.5,结果完全一样——只有兄弟元素之间的比值有意义3

完整示例(跟着做)

一个三栏布局,中间栏的弹性是两侧的两倍:

因为三个都用 flex-basis: 0,整个宽度都是自由空间,按增长因子 1 : 2 : 1 划分。因子总数是 4,所以每个 .side 拿到宽度的四分之一,.main 拿到一半。这里 flex: 2 确实意味着“两倍于一侧”,但只因为 basis 是 0,它把整条行都变成了自由空间。把 basis 改成 auto,比例就会作用在剩余空间上了。

你来试试(填空示例)

你想要一个固定 240px 的侧边栏,和一个吃掉其余全部空间的内容区。填上两个 flex 简写:

答案:.sidebar { flex: 0 0 240px; }(grow 0、shrink 0、basis 240px,它原地不动)和 .content { flex: 1; }(grow 1,吸收全部自由空间)。把每个简写读成“grow / shrink / basis”,意图一目了然:侧边栏拒绝伸缩,内容区拿走剩下的。

小结 + 下节课预告

flex-basis 设定 item 的起始尺寸;flex-growflex-shrink 按各自因子的比例划分正或负的自由空间flex 简写把三者打包,flex: 1 等于 1 1 0。到目前为止一切都假设 item 只有一行。最后一课我们用 flex-wrap 让 item 换到多行、用 gap 拉开间距,然后你从零搭一个真实的响应式导航栏和卡片行。

Footnotes

  1. MDN: Controlling ratios of flex items along the main axis — https://developer.mozilla.org/en-US/docs/Web/CSS/Guides/Flexible_box_layout/Controlling_flex_item_ratios 2 3 4 5 6 7 8 9 10 11 12

  2. CSS-Tricks: A Complete Guide to Flexbox — https://css-tricks.com/snippets/css/a-guide-to-flexbox/

  3. MDN: Basic concepts of flexbox — https://developer.mozilla.org/en-US/docs/Web/CSS/Guides/Flexible_box_layout/Basic_concepts 2 3

Exercises

01

在你自己的文件里,搭一个 display: flex 的行,两个子元素。给其中一个 flex-grow: 1,另一个保持默认。刷新,看哪一个吸收了空白。然后给两个都设 flex-grow: 1。

第 1 级(热身)
Done criteria · checked locally
02

在一条宽于 400px 的行里,把两个 item 分别设为 flex: 1 1 200px 和 flex: 3 1 200px。刷新前先预测各自的最终宽度,然后在 devtools 里量。

第 2 级(进阶)
Done criteria · checked locally

My note

Jot down thoughts, sticking points, things you didn't get. Written to this course's appendix only — the lesson file is never touched.