Skip to content

交互式演示教程教会你精通掌握 Flexbox 布局

Introduction

Flexbox 是一种非常强大的布局模式。当我们真正了解了它的工作原理后,就能构建自动响应的动态布局,并根据需要重新排列。

例如,看看这个:

600
Drag me!

本演示深受 Adam Argyle 令人难以置信的 codepen “4 layouts for the price of 1” 的启发。它不使用媒体/容器查询。 它没有设置任何的折行点,而是利用流体原理创建了一个流畅的布局。

下面是相关的 CSS:

css

我记得自己在观看这样的演示时,完全摸不着头脑。我知道 Flexbox 的基本原理,但这似乎是绝对的魔法!

在本篇博文中,我希望完善您的 Flexbox 心智模型。 我们将通过了解这些属性中的每一个,建立对 Flexbox 算法工作原理的直观认识。无论你是 CSS 初学者,还是使用 Flexbox 多年的老手,我打赌你都能学到不少东西!

我们开始吧

Link to this heading
Flexbox 简介

CSS 包含多种不同的布局算法,正式名称为 “布局模式”。每种布局模式都是 CSS 中的一种子语言。默认的布局模式是流式布局,但我们可以通过更改父容器上的 display 属性,选择使用 Flexbox:

Hello
to
the
world!
Primary Axis
Primary Axis
display:

当我们将 display 设置为 flex 时,我们会创建一个 “flex 模式上下文”。这意味着,默认情况下,所有子元素都将按照 Flexbox 布局算法进行摆放。

Clippy, the helpful paperclip assistant from Microsoft Word每种布局算法都是为解决特定问题而设计的。 默认的“流式”布局旨在创建数字文档;它基本上是微软 Word 的布局算法。标题和段落以块的形式垂直堆叠,而文本、链接和图片等内容则位于这些块中,并不显眼。

那么,Flexbox 能解决什么问题呢? Flexbox 就是在一行或一列中排列一组元素,并赋予我们对这些元素的分布和对齐方式的极大控制权。顾名思义,Flexbox 就是灵活性。我们可以控制元素的拉伸或收缩,以及额外空间的分配等等。

Link to this heading
Flex 排列方向

如前所述,Flexbox 就是要控制元素在一行或一列中的分布。默认情况下,元素将并排堆叠在一行中,但我们可以使用 flex-direction 属性翻转到一列中:

Hi there!
to
the
world
Primary Axis
Primary Axis
flex-direction:

使用 flex-direction: row 时,主轴是水平的,从左到右。当我们切换到 flex-direction: column 时,主轴会从上到下垂直排布。

在 Flexbox 中,一切都基于主轴(Primary axis)。 算法并不关心垂直/水平,甚至行/列。所有规则都是围绕主轴和垂直于主轴的纵轴(Cross axis)来构建的。

这很酷。 当我们掌握了 Flexbox 的规则后,就可以从水平布局无缝切换到垂直布局。所有规则都会自动调整。这一功能是 Flexbox 布局模式所独有的。

默认情况下,子元素们将根据以下两条规则进行排列:

  1. 主轴方向(Primary axis): 子元素将集中在容器的起始位置。
  2. 纵轴方向(Cross axis): 子元素将伸展开来,填满整个容器。

下面是这些规则的快速可视化示意图:

在 Flexbox 中,我们先要设定主轴是横向还是纵向。这是所有 Flexbox 布局计算的根基。

我们可以使用 justify-content 属性更改子元素沿主轴的分布方式:

Hello
to
the
World
Primary Axis
Primary Axis
row
flex-start

说到主轴,它通常不是针对对齐某一个子元素。相反,它是针对 整个组的排列。

我们可以将所有元素集中在一个特定的位置(使用 flex-start, center,和 flex-end),也可以将它们分散开来(使用 space-between, space-around, 和 space-evenly)。

纵轴的情况则有些不同。 我们使用 align-items 属性:

Hello
to
the
World
Primary Axis
Primary Axis
row
flex-start
stretch

有趣的是,对于 align-items,我们有一些与 justify-content 相同的选项,但并不完全重合。

justify-content
align-items
flex-start
center
flex-end

为什么它们不共享相同的选项呢?我们很快就会揭开这个谜团,但首先,我需要再分享一个对齐属性:align-self

justify-contentalign-items 不同, align-self 适用于子元素,而不是容器。它允许我们改变特定子元素沿纵轴的对齐方式:

Hello
to
the
world
Primary Axis
Primary Axis
row
stretch

align-self 的值与 align-items完全相同。事实上,它们改变的是完全相同的东西。align-items is 是一种语法糖,是一种方便的速记法,可以自动一次性设置所有子元素的对齐方式。

没有 justify-self。要理解为什么没有,我们需要深入研究 Flexbox 算法。

Link to this heading
Content vs. items

因此,根据我们目前所学到的知识,Flexbox 似乎很武断。为什么是 justify-contentalign-items,而不是 justify-items, 或 align-content?

那么,为什么有 align-self,却没有 justify-self呢??

这些问题涉及 Flexbox 最重要也是最容易被误解的一点。 为了帮助我解释,我想用一个比喻来说明。

在 Flexbox 中,子元素沿主轴分布。默认情况下,它们是并排整齐排列的。我可以画一条水平直线,将所有子元素串起来,就像 一样?

但纵轴是不同的。一条垂直直线只会与其中一个子元素相交。

它不像烤肉串,更像一排

这里有一个显著的区别。有了烤肠,每件物品都可以沿着自己的签子移动,而不会影响其他任何物品:

Drag me!

相比之下,由于我们的主轴将每个子元素串成一线,单个元素无法在不撞到兄弟姐妹的情况下沿着主轴移动 试着左右拖动中间部分:

这就是主轴/纵轴之间的根本区别。 当我们在纵轴上讨论排列时,每个子元素都可以随心所欲。但在主轴上,我们只能考虑如何 分组排布

这就是为什么没有 justify-self的原因。 如果中间的元素设置成 justify-self: flex-start会怎样? 已经有元素在那里了!

有了这些背景,让我们来给我们一直在谈论的这四个术语下一个正确的定义:

  • justify — 沿主轴排列。
  • align — 沿纵轴排列。
  • content — 一组可以排列摆放的“东西”
  • items — 一个可以单独摆放的东西

因此,我们使用 justify-content 来控制组沿主轴的分布,使用 align-items 来沿纵轴单独定位每个项目。这就是我们使用 Flexbox 管理布局的两个主要属性。

没有 justify-items 的原因与没有 justify-self的原因相同;当涉及主轴时,我们必须将子元素视为一个组,视为可以排列的内容。

那么 align-content 呢?实际上,Flexbox 中确实存在对齐内容!我们稍后会在讨论 flex-wrap 属性时介绍它。

让我们来谈谈我对 Flexbox 的一个最直观的认识。

假设我有以下 CSS:

css

一个理智的人看到这里可能会说:“好吧,我们将得到一个 2000 像素宽的子元素”。 但事实会一定如此吗?

让我们来测试一下:

Code Playground

Result

Enable ‘tab’ key

这很有趣,不是吗?

两个项目都应用了完全相同的 CSS。 它们的宽度都是 width: 2000px。然而,第一项比第二项宽得多!

区别在于布局模式第一个项目是使用流式(Flow) 布局呈现的,而在流式布局中, width 是一个硬约束。当我们设置 width: 2000px时,我们将得到一个 2000 像素宽的元素,即使它会因此而撑破容器、冲出 viewport。

但在 Flexbox 布局中, width 属性的实现方式有所不同。它更像是一种建议,而不是硬性约束。

规范对此有一个名称: hypothetical size/假设尺寸/愿望尺寸。这是在一个完美的乌托邦世界里,在没有任何阻碍的情况下,一个元素的尺寸。

唉,事情很少这么简单。在这种情况下,限制因素是父容器没有空间容纳一个 2000px 宽的子元素。因此,缩小了子元素的尺寸,使其能够适应。

这是 Flexbox 理念的核心部分。事物是流动的、灵活的,可以根据世界的限制进行调整。

Link to this heading
膨胀和收缩

因此,我们已经看到 Flexbox 算法具有一定的内置灵活性,可以使用“愿望尺寸”。但要真正了解 Flexbox 的灵活性,我们还需要了解 3 个属性:flex-grow, flex-shrink, 和 flex-basis

让我们来看看每个属性。

我承认:很长时间以来,我都不太明白这个 flex-basis 该怎么用。 😅

简单地说: 在 Flexbox 行中, flex-basis 的作用与 width相同。在 Flexbox 列中, flex-basis 的作用与 height 相同。

正如我们已经了解到的,Flexbox 中的一切都与主轴/纵轴挂钩。例如, justify-content 将沿主轴排列子元素,而且无论主轴是水平还是垂直,其工作方式都完全相同。

widthheight——然而——不遵循这一规则! width 始终会影响水平尺寸。当我们将 flex-direction 的值从 row 改变成 column后,这个宽度不会突然就变成了 height 高度。

因此,Flexbox 的作者创建了一个名为 flex-basis 的通用“尺寸”属性。 它类似于 widthheight,但与其他属性一样,与主轴挂钩。它允许我们设置元素在主轴方向上的“愿望尺寸”,而不管是水平方向还是垂直方向。

在这里试一试。每个子元素都有 flex-basis: 50px 的宽度,但你可以调整第一个子元素:

Primary Axis
Primary Axis
flex-direction:
50

就像我们看到的 width一样, flex-basis 更像是一种建议,而不是硬性约束。在某一点上,没有足够的空间让所有元素都达到指定的大小,因此它们必须妥协,以避免溢出。

默认情况下,Flexbox 上下文中的元素会沿主轴缩小到最小舒适尺寸。这通常会产生额外的空间。

我们可以使用 flex-grow 属性来指定占有空间的方式:

Extra Space
Primary Axis
Primary Axis
flex-direction:
flex-grow:

flex-grow的默认值是 0,这意味着是否膨胀是看情况的。如果我们想让子元素吞噬容器中的多余空间,就需要明确告诉它。

如果多个子元素设置了 flex-grow,该怎么办?在这种情况下,额外的空间会根据子元素的 flex-grow 值按比例分配给子元素。

我想这样更容易直观地解释清楚。试着递增/递减每个子元素:

flex-grow:
1
2
flex-grow:
1
2
Primary Axis
Primary Axis

第一个子元素想要 1 个单位的额外空间,第二个子元素想要 1 个单位的。也就是说,单位总数是 2 (1 + 1)。每个孩子都能按比例分享额外的空间。


在我们目前看到的大多数例子中,我们都有额外的空间可以利用。但是,如果我们的子元素对他们的容器来说太大了怎么办?

让我们来测试一下。试着缩小容器,看看会发生什么:

flex-basis
300px
Actual size:
300px
Reduced by:
0%
flex-basis
150px
Actual size:
150px
Reduced by:
0%
Primary Axis
Primary Axis
600

有趣吧?两个元素都会缩小,但它们是按比例缩小的。第一个子项的宽度始终是第二个子项宽度的 2 倍

友情提示:flex-basis 的作用与 width 相同。我们使用 flex-basis 是因为它很传统,但如果我们使用 width,也会得到完全相同的结果!

flex-basiswidth 设置元素的愿望尺寸。Flexbox 算法可能会将元素缩小到这一理想尺寸以下,但默认情况下,它们将始终一起缩放,保持两个元素之间的比例。

现在,如果我们不想让元素按比例缩小怎么办?这就需要使用 flex-shrink 属性了。

花几分钟看看这个演示看看你能不能搞清楚这里发生了什么。 我们将在下面进行探讨。

flex-basis
250px
flex-shrink
1
Actual size:
250px
Reduced by:
0%
flex-basis
250px
flex-shrink
1
Actual size:
250px
Reduced by:
0%
Primary Axis
Primary Axis
1
600

好吧:我们有两个子元素,每个子元素的愿望大小为 250px。容器至少需要 500px 宽,才能容纳假设大小的这两个子元素。

假设我们把容器缩小到 400px。那么,我们就无法将 500px 的内容塞进 400px 的容器中!我们有 100px 的缺口。我们的元素总共需要让出 100px 的空间才能容纳。

这个 flex-shrink 属性可让我们决定如何分配剩余空间。

flex-grow 一样,它也是一个比率。默认情况下,两个子元素值都是 flex-shrink: 1,因此每个子元素都让出空间的 1/2。他们各自放弃 50px,实际大小从 250px 缩小到 200px。

现在,让我们假设第一个子元素值增加到 flex-shrink: 3

Screenshot showing the demo above, locked at 400px wide, with the first child having a flex-shrink of 3

我们的总压缩空间为 100px。通常情况下,每个子元素需要拿出 ½ 的空间,但由于我们使用了 flex-shrink,第一个元素需要拿出 ¾ 的空间(75px),第二个元素需要拿出 ¼ 的空间(25px)。

注意,绝对值并不重要,关键是比例。如果两个子元素的值都是 flex-shrink: 1,那么每个子元素将拿出总压缩空间的 1/2。如果两个子元素的值都是 flex-shrink: 1000,那么每个子元素将拿出总压缩空间的 1000/2000。无论哪种方式,结果都是一样的。


前不久,我对 flex-shrink 有了一个顿悟:我们可以把它看作是 flex-grow 的 “反义词”。它们是一枚硬币的两面:

  • flex-grow 当子元素小于其容器时,它会控制额外空间的分配方式。
  • flex-shrink 可以控制当子元素大于其容器时如何剪掉空间。

这意味着这些属性中只有一个可以同时激活。如果有多余的空间, flex-shrink 没有任何作用,因为元素不需要缩小。如果子元素对于容器来说太大了, flex-grow 也不起作用,因为没有多余的空间可以分割。

我认为这是两个不同的世界。你要么在地球,要么在倒镜世界。每个世界都有自己的规则。

有时,我们不希望我们的一些 Flex 子元素收缩。

我经常在使用 SVG 图标和图形时发现这种情况。让我们来看一个简化的例子:

Primary Axis
Primary Axis
600

当容器变窄时,我们的两个圆形就会被挤压成毛茸茸的椭圆形。如果我们想让它们保持圆形呢?

我们可以通过设置 flex-shrink: 0来实现:

Primary Axis
Primary Axis
flex-shrink:
600

当我们将 flex-shrink 为 0 时,我们基本上就完全“退出 ”了收缩过程。Flexbox 算法会将 flex-basis (或 width) 视为硬性的最小限制。

如果你好奇,这里有这个演示的完整代码:

Flex Shrink ball demo

Result

Enable ‘tab’ key

Link to this heading
最小尺寸难题

在这里,我们还需要谈一件事,而且这件事超级重要。它可能是整篇文章中最有帮助的一件事!

假设我们要为一家电子商务商店创建一个流体搜索表单:

500

当容器缩小到某一点以下时, 内容就会溢出!

可是为什么?? flex-shrink 的缺省值是 1,我们也没有删除它,所以搜索输入应该可以根据需要缩小!为什么它拒绝缩小?

情况是这样的:除了愿望尺寸外,Flexbox 算法还关心另一个重要尺寸 最小尺寸

Flexbox 算法拒绝将子元素缩小到最小尺寸以下。 无论我们将 flex-shrink 调到多高,内容都会溢出,而不会进一步缩小!

文本输入框的默认最小尺寸为 170px-200px(因浏览器而异)。这就是我们遇到的限制。

在其他情况下,限制因素可能是元素的内容。例如,试试调整这个容器的大小:

The longest word in this item is “sesquipedalian”.
This one has no long words.
Primary Axis
Primary Axis
600

对于包含文本的元素,最小宽度为最长的不可断开字符串的长度。

好消息是:我们可以使用 min-width 属性重新定义最小尺寸。

min-width:
500

通过直接在 Flex 子元素上设置 min-width: 0px,我们可以告诉 Flexbox 算法覆盖“内置”的最小宽度。因为我们将其设置为 0px,所以元素可以根据需要缩小。

同样的技巧在使用 min-height 属性的 Flex 列中也能奏效(不过这个问题似乎不常出现)。

近年来,Flexbox 对编程质量的最大改进之一就是 gap 属性:

Primary Axis
Primary Axis
flex-direction:
4px

gap 允许我们在每个 Flex 子项之间创建空隙。这非常适合导航标题等内容:

Primary Axis
Primary Axis
justify-content:
16px

gap 是 Flexbox 语言的一个相对较新的新增功能,但自 2021 年初以来,它已在所有现代浏览器中实现。

我还想分享一个与空间相关的技巧。它在 Flexbox 早期就已经存在了,但相对来说比较隐晦,我第一次发现它时大吃一惊。

这个 margin 属性用于在特定元素周围添加空间。在某些布局模式下,如 “流”(Flow)和 “定位”(Positioned),它甚至可以用来将元素居中,即 margin: auto

在 Flexbox 中,自动边距要有趣得多:

Primary Axis
Primary Axis
margin-left:
margin-right:

前面,我们看到了 flex-grow 属性如何吞噬多余空间,并将其应用于子元素。

自动边距 会吞噬多余的空间,并将其应用到元素的边距中。 这样我们就能精确控制多余空间的分布位置。

常见的 header 布局是一边是logo,另一边是一些导航链接。下面我们就来看看如何使用自动页边距构建这种布局:

Code Playground

Result

Enable ‘tab’ key

这个 Corpatech logo 是列表中的第一个列表项。通过 margin-right: auto,我们收集了所有多余的空间,并将其强制放在第一项和第二项之间。

我们可以使用浏览器开发工具查看这里发生了什么:

Screenshot showing the above playground with an orange rectangle representing margin between the first item and the others

我们还有很多其他方法可以解决这个问题:我们可以将导航链接分组到自己的 Flex 容器中,或者使用 flex-grow增加第一个列表项。但就我个人而言,我喜欢自动边距解决方案。我们将多余的空间视为一种资源,并决定它应该放在哪里。

呼!到目前为止,我们已经讲了很多内容。我还想分享一个重要的收获。

到目前为止,我们的所有项目都是并排放置在一行/一列中。 flex-wrap 属性允许我们改变这种情况。

Check it out:

flex-basis
180px
Actual size
180px
flex-basis
180px
Actual size
180px
flex-basis
180px
Actual size
180px
Primary Axis
Primary Axis
flex-wrap:
600

大多数情况下,当我们在二维空间中工作时,我们会希望使用 CSS Grid,但 Flexbox + flex-wrap 绝对有它的用武之地!这个例子展示了 “解构薄饼” 布局,在中型屏幕上,3 个项目堆叠成一个倒金字塔。

当我们设置了 flex-wrap: wrap 时, 子元素就不会缩小到愿望尺寸以下。至少,在可以选择换行/换列的情况下不会!

但是,等等!等等!我们的烤肉串/腊肠的比喻是什么?

有了 flex-wrap: wrap,我们就不再需要一条主轴线来串联每个项目了。实际上, 每一行都是自己的小型柔性容器。每一行都有自己的串行线,而不是一个大串行线:

在缩小的范围内,我们迄今为止学到的所有规则仍然适用。例如, justify-content 将在每根棍子上分配两个肉串。

但是...现在我们有了多行,如何使用 align-items 呢?现在纵轴可以与多个元素相交!

花点时间考虑一下。如果我们改变这个属性,你认为会发生什么?一旦你有了答案(或至少有了一个想法),看看它是否正确:

Primary Axis
Primary Axis
align-items:

每一行都是自己的迷你 Flexbox 环境。align-items 将在环绕每一行的隐形框内上下移动每个项目。

但如果我们想对齐行本身呢?我们可以使用 align-content 属性来实现:

Primary Axis
Primary Axis
flex-start
flex-start

总结一下这里发生的事情:

  • flex-wrap: wrap 提供了两行内容。
  • 在每一行中, align-items 可以让我们上下滑动每个子元素
  • 然而,将视图缩小后,我们就可以在一个 Flex 上下文中看到这两行!现在,纵轴将与两行相交,而不是一行。因此,我们不能单独移动这两行,而需要将它们 作为一组进行分配。
  • 根据上面的定义,我们处理的是内容,而不是项目。但我们仍在讨论纵轴!因此,我们需要的属性是 align-content

因此,我想承认一点:这是一篇内容繁杂的教程。我们已经深入了兔子洞,除非你已经是 Flexbox 专家,否则我估计你会有点头晕。 😅

就像 CSS 中的很多内容一样,Flexbox 在刚开始使用时可能看起来很简单,但当你掌握了基础知识后,其复杂性就会迅速增加。

因此,我们中的许多人在使用 CSS 时很早就陷入了困境。我们知道足够多的知识来完成工作,但却一直在苦苦挣扎。这门语言给人的感觉是摇摇欲坠、难以捉摸,就像一座古老的索桥,随时都可能断裂。一旦出现问题,我们就会在 StackOverflow 上随意搜索一些片段,希望能有所帮助。

这一点都不好玩。这很糟糕,因为 CSS 是大多数前端开发工作的重要组成部分!

事实上,CSS 是一种非常强大和一致的语言。问题在于,我们的大部分心智模型都不完整、不准确。当我们花时间建立起对这门语言的正确直觉时,一切就会变得顺理成章,CSS 的 使用也会变得轻松愉快。. ✨

Link to this heading
红包:解密演示

在本教程的开头,我们看到了以下 “4 种布局只需 1 种设置”的演示:

600
Drag me!

既然我们已经了解了 Flexbox 算法的全部内容,那么您能找出它是如何工作的吗?请随意尝试这里的代码:

Code Playground

Result

Enable ‘tab’ key

感谢您的阅读!这篇博文耗费了我大量的心血,我很高兴能将它公之于众!希望对你有用 💖


Last Updated

June 27th, 2023

Hits