第 4 课:剩余空间的分配
本课目标:
- 解释
flex-basis、flex-grow、flex-shrink各自控制什么。- 预测
flex-grow如何划分自由空间(不是总宽度)。- 读懂
flex简写,并知道flex: 1展开成什么。先修条件:第 1-3 课(两条轴、
justify-content、align-items、flex-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: 2、flex-shrink: 1、flex-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 的负自由空间1。flex-grow 只作用于正的那种,flex-shrink 只作用于负的那种。如果你设了增长因子却什么都没发生,通常的原因就是根本没有正自由空间可分。
再看不对称。增长只按因子划分自由空间。收缩则要先把每个 item 的收缩因子乘上它的基础尺寸再分配,这样扣除量就按“各自还能让出多少”来摊——所以小 item 不会在大 item 明显变窄之前就被压到零1。除此之外,item 不会收缩到低于它的 min-content 尺寸,也就是仍能容纳最长那个词的最小尺寸1。这两条规则加在一起,既解释了为什么 flex-shrink 比 flex-grow 难预测,也解释了为什么收缩很少真的把内容毁掉。
item 起步用的那个尺寸自己也有一套词汇。min-content 大致是元素仍能装下最长单词的最小尺寸;max-content 是它要装下全部内容且不换行所需要的尺寸1。当 flex-basis 是 auto 且 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 的值是比例,不是单位。写 1 和 2 与写 10 和 20、或者 .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-grow 和 flex-shrink 按各自因子的比例划分正或负的自由空间;flex 简写把三者打包,flex: 1 等于 1 1 0。到目前为止一切都假设 item 只有一行。最后一课我们用 flex-wrap 让 item 换到多行、用 gap 拉开间距,然后你从零搭一个真实的响应式导航栏和卡片行。
Footnotes
-
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
-
CSS-Tricks: A Complete Guide to Flexbox — https://css-tricks.com/snippets/css/a-guide-to-flexbox/ ↩
-
MDN: Basic concepts of flexbox — https://developer.mozilla.org/en-US/docs/Web/CSS/Guides/Flexible_box_layout/Basic_concepts ↩ ↩2 ↩3
练习
在一条宽于 400px 的行里,把两个 item 分别设为 flex: 1 1 200px 和 flex: 3 1 200px。刷新前先预测各自的最终宽度,然后在 devtools 里量。
第 2 级(进阶)我的笔记
记下想法、痛点、没懂的地方。只写进这门课的附录,正课文件不动。