CSS Flexbox:弹性布局入门 · 第 5 / 5 节

第 5 课:换行与真实布局

本课目标:

  • flex-wrap: wrap 让 item 流到多行,而不是溢出容器。
  • gap 拉开 item 间距,代替琐碎的 margin。
  • 用整门课学到的东西,搭一个真实的响应式导航栏和卡片行。

先修条件:第 1-4 课(两条轴、对齐、flex-directionflex-grow) | 上一篇 << 04

六张卡片挤在一行,成了一团压扁的乱局

你有一行卡片。宽屏上放得下。窄屏上它们要么溢出容器,要么被压得极窄,因为默认情况下 flex 行拒绝断行:不管怎样它都把所有东西留在同一行。就这一个默认值,毁掉了大量“响应式”卡片布局。修法是一个属性放行换行,另一个属性拉开间距。然后我们把整门课拼起来,搭出你真正会上线的那种布局。

讲解

flex-wrap 的默认值是 nowrap,意思是一组对容器来说太宽的 item 会溢出它1。这就是压扁或溢出的来历。要让 item 断到新行,在容器上设 flex-wrap: wrap,item 在空间不够时就会换到新的行上1

只换行还可能让 item 边贴边挤在一起。干净的间距做法是容器上的 gap 属性:它在相邻 flex item 之间创建固定间距,并且是 row-gapcolumn-gap 的简写1。关键在于 gap 只把间距放在 item 之间,不放在外边缘,所以你不会碰上 margin 常见的双倍间距和边缘溢出的麻烦2

下面的网格里有六张固定宽度的卡片被迫挤在一行,所以它们溢出了。让它们换到多行,并均匀拉开间距。

注意这些卡片自己也是 display: flex:每张卡片里的数字正是用第 2 课的 justify-contentalign-items 居中的。flex 容器可以自由嵌套:网格安排卡片,每张卡片安排自己的内容。

每一行换行出来的行都是独立的 flex 容器

item 一旦换行,有件事会变,而它常常把人绊住:每一条 flex 行都像一个独立的 flex 容器。空间分配在每一行内部各自完成,完全不参考上一行或下一行3。Flexbox 里没有任何特性能让某一行的 item 去和上一行的 item 对齐成列1

这一个事实就解释了换行里最常被报告的意外。放一排可以增长的 item——比如 flex: 1 1 160px——最后一行装的个数通常比上面几行少。因为那一行是自己的容器、有自己的自由空间要分,它里面的 item 就会比楼上的兄弟更宽。如果最后一行只剩一个 item,它会拉伸到填满整行1。这没坏;你要的是一维分配,拿到的就是逐行的一维分配。

这也是这个工具诚实的边界。Flexbox 一次只控制一条轴;CSS Grid 同时控制行和列,而且grid item 会留在自己的格子里,不会因为最后一行少几个就拉伸1。如果你发现自己在给 flex item 加百分比宽度硬凑成列、或者插入空 item 去占位,那就是该把这个组件换成 Grid 的信号1。“希望空间逐行分配”是 Flexbox 的场景;“希望要一个真正的二维网格”不是。

align-content:只有换行之后才存在的属性

align-items 是在交叉轴上把 item 定位在它所在的那一行之内。当出现多行时,另一个问题才浮现:这一组行本身该怎么分布在交叉轴剩下的空间里?这就是 align-content,有时也被描述为“打包 flex 行”4

有两个条件在管着它,少一个它就像什么都没做,而这正是它常被误判为失效的原因。容器必须真的在换行,并且交叉轴方向的尺寸必须大于摆下这些行所需要的——否则根本没有空间可分4。它接受那套熟悉的词汇(flex-startcenterspace-betweenspace-aroundspace-evenlystretch),初始值的行为等同于 stretch4

值得把它和 gap 摆在一起看,因为两者很容易混:row-gap 设定的是行与行之间一个固定的距离,而 align-content 分配的是交叉轴上剩下的那部分空间。它们是可以叠加的——而且它们、连同 margin 和 padding,都会影响 item 之间肉眼可见的距离1

完整示例(跟着做)

一个响应式标签列表,任意宽度下都能换行并保持均匀间距:

用整门课的知识读它:display: flex 把标签摆上主轴(第 1 课)。flex-wrap: wrap 让它们在一行放满时断到新行,而不是溢出。gap: 8px 在每个标签之间放上均匀的水平和垂直间距,也不会给一行末尾的标签多加 margin。缩放窗口,标签自己重新排布,换行本身不需要任何媒体查询。

你来试试(填空示例)

你想要一个照片墙:每张照片 150px 宽,网格可以换行,照片之间留 16px 间距。填上容器的三条声明:

答案:display: flex;flex-wrap: wrap;gap: 16px;。三行正好对应三个概念:变成一条 flex 行、允许这条行断开、把碎块之间的距离拉开。这套组合是你用 Flexbox 搭的几乎每个响应式网格的骨架。

小结 + 下节课预告

flex-wrap: wrap 让 flex 行断到多行而不是溢出,gap 不碰外边缘、干净地拉开 item 间距。和前几课的 display: flex、两条轴、flex-directionflex-grow 拼在一起,你已经有了居中、导航栏、空间分配和换行卡片行的整套工具。你不再猜该伸手拿哪个属性,你在读轴。这就是被布局拖着走和指挥布局的差别。

Footnotes

  1. MDN: Mastering wrapping of flex items — https://developer.mozilla.org/en-US/docs/Web/CSS/Guides/Flexible_box_layout/Wrapping_items 2 3 4 5 6 7 8

  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

  4. MDN: Aligning items in a flex container — https://developer.mozilla.org/en-US/docs/Web/CSS/Guides/Flexible_box_layout/Aligning_items 2 3

练习

01

在你自己的文件里,搭一行六张固定宽度卡片的 display: flex。把浏览器窗口缩窄,直到它们溢出。加上 flex-wrap: wrap 并刷新,然后再加 gap。看着卡片重新排成多行。

第 1 级(热身)
完成标准 · 本地勾选
02

在你自己的编辑器里新建真实的 index.html 和 style.css。只用 Flexbox 搭一个顶部导航栏和一个卡片行,然后在浏览器里打开文件,用 devtools 验证:

第 2 级(结课作业:搭一个真实页面)
  1. 一个导航栏:logo 在左,中间的搜索输入框长开填满空间(flex-grow),右边一个“Sign in”按钮,全部垂直居中。
  2. 下面是一行六张卡片,随着窗口缩放换到多行,gap 间距均匀。
  3. 打开 devtools,选中导航栏和卡片网格,读出它们的计算 flex 属性。
完成标准 · 本地勾选

我的笔记

记下想法、痛点、没懂的地方。只写进这门课的附录,正课文件不动。