Introduction
Flexbox 是一种非常强大的布局模式。当我们真正了解了它的工作原理后,就能构建自动响应的动态布局,并根据需要重新排列。
例如,看看这个:
本演示深受 Adam Argyle 令人难以置信的 codepen “4 layouts for the price of 1” 的启发。它不使用媒体/容器查询。 它没有设置任何的折行点,而是利用流体原理创建了一个流畅的布局。
下面是相关的 CSS:
css
我记得自己在观看这样的演示时,完全摸不着头脑。我知道 Flexbox 的基本原理,但这似乎是绝对的魔法!
在本篇博文中,我希望完善您的 Flexbox 心智模型。 我们将通过了解这些属性中的每一个,建立对 Flexbox 算法工作原理的直观认识。无论你是 CSS 初学者,还是使用 Flexbox 多年的老手,我打赌你都能学到不少东西!
我们开始吧
Link to this headingFlexbox 简介
CSS 包含多种不同的布局算法,正式名称为 “布局模式”。每种布局模式都是 CSS 中的一种子语言。默认的布局模式是流式布局,但我们可以通过更改父容器上的 display
属性,选择使用 Flexbox:
当我们将 display
设置为 flex
时,我们会创建一个 “flex 模式上下文”。这意味着,默认情况下,所有子元素都将按照 Flexbox 布局算法进行摆放。
每种布局算法都是为解决特定问题而设计的。 默认的“流式”布局旨在创建数字文档;它基本上是微软 Word 的布局算法。标题和段落以块的形式垂直堆叠,而文本、链接和图片等内容则位于这些块中,并不显眼。
那么,Flexbox 能解决什么问题呢? Flexbox 就是在一行或一列中排列一组元素,并赋予我们对这些元素的分布和对齐方式的极大控制权。顾名思义,Flexbox 就是灵活性。我们可以控制元素的拉伸或收缩,以及额外空间的分配等等。
Link to this headingFlex 排列方向
如前所述,Flexbox 就是要控制元素在一行或一列中的分布。默认情况下,元素将并排堆叠在一行中,但我们可以使用 flex-direction
属性翻转到一列中:
使用 flex-direction: row
时,主轴是水平的,从左到右。当我们切换到 flex-direction: column
时,主轴会从上到下垂直排布。
在 Flexbox 中,一切都基于主轴(Primary axis)。 算法并不关心垂直/水平,甚至行/列。所有规则都是围绕主轴和垂直于主轴的纵轴(Cross axis)来构建的。
这很酷。 当我们掌握了 Flexbox 的规则后,就可以从水平布局无缝切换到垂直布局。所有规则都会自动调整。这一功能是 Flexbox 布局模式所独有的。
默认情况下,子元素们将根据以下两条规则进行排列:
- 主轴方向(Primary axis): 子元素将集中在容器的起始位置。
- 纵轴方向(Cross axis): 子元素将伸展开来,填满整个容器。
下面是这些规则的快速可视化示意图:
在 Flexbox 中,我们先要设定主轴是横向还是纵向。这是所有 Flexbox 布局计算的根基。
我们可以使用 justify-content
属性更改子元素沿主轴的分布方式:
说到主轴,它通常不是针对对齐某一个子元素。相反,它是针对 整个组的排列。
我们可以将所有元素集中在一个特定的位置(使用 flex-start
, center
,和 flex-end
),也可以将它们分散开来(使用 space-between
, space-around
, 和 space-evenly
)。
纵轴的情况则有些不同。 我们使用 align-items
属性:
有趣的是,对于 align-items
,我们有一些与 justify-content
相同的选项,但并不完全重合。
为什么它们不共享相同的选项呢?我们很快就会揭开这个谜团,但首先,我需要再分享一个对齐属性:align-self
。
与 justify-content
和 align-items
不同, align-self
适用于子元素,而不是容器。它允许我们改变特定子元素沿纵轴的对齐方式:
align-self
的值与 align-items
完全相同。事实上,它们改变的是完全相同的东西。align-items
is 是一种语法糖,是一种方便的速记法,可以自动一次性设置所有子元素的对齐方式。
没有 justify-self
。要理解为什么没有,我们需要深入研究 Flexbox 算法。
Link to this headingContent vs. items
因此,根据我们目前所学到的知识,Flexbox 似乎很武断。为什么是 justify-content
和 align-items
,而不是 justify-items
, 或 align-content
?
那么,为什么有 align-self
,却没有 justify-self
呢??
这些问题涉及 Flexbox 最重要也是最容易被误解的一点。 为了帮助我解释,我想用一个比喻来说明。
在 Flexbox 中,子元素沿主轴分布。默认情况下,它们是并排整齐排列的。我可以画一条水平直线,将所有子元素串起来,就像 烤肉串?一样?
但纵轴是不同的。一条垂直直线只会与其中一个子元素相交。
它不像烤肉串,更像一排 烤肠?:
这里有一个显著的区别。有了烤肠,每件物品都可以沿着自己的签子移动,而不会影响其他任何物品:
相比之下,由于我们的主轴将每个子元素串成一线,单个元素无法在不撞到兄弟姐妹的情况下沿着主轴移动 试着左右拖动中间部分:
这就是主轴/纵轴之间的根本区别。 当我们在纵轴上讨论排列时,每个子元素都可以随心所欲。但在主轴上,我们只能考虑如何 分组排布。
这就是为什么没有 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
这很有趣,不是吗?
两个项目都应用了完全相同的 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
。
让我们来看看每个属性。
Link to this headingflex-basis
我承认:很长时间以来,我都不太明白这个 flex-basis
该怎么用。 😅
简单地说: 在 Flexbox 行中, flex-basis
的作用与 width
相同。在 Flexbox 列中, flex-basis
的作用与 height
相同。
正如我们已经了解到的,Flexbox 中的一切都与主轴/纵轴挂钩。例如, justify-content
将沿主轴排列子元素,而且无论主轴是水平还是垂直,其工作方式都完全相同。
width
和 height
——然而——不遵循这一规则! width
始终会影响水平尺寸。当我们将 flex-direction
的值从 row
改变成 column
后,这个宽度不会突然就变成了 height
高度。
因此,Flexbox 的作者创建了一个名为 flex-basis
的通用“尺寸”属性。 它类似于 width
或 height
,但与其他属性一样,与主轴挂钩。它允许我们设置元素在主轴方向上的“愿望尺寸”,而不管是水平方向还是垂直方向。
在这里试一试。每个子元素都有 flex-basis: 50px
的宽度,但你可以调整第一个子元素:
就像我们看到的 width
一样, flex-basis
更像是一种建议,而不是硬性约束。在某一点上,没有足够的空间让所有元素都达到指定的大小,因此它们必须妥协,以避免溢出。
Link to this headingflex-grow
默认情况下,Flexbox 上下文中的元素会沿主轴缩小到最小舒适尺寸。这通常会产生额外的空间。
我们可以使用 flex-grow
属性来指定占有空间的方式:
flex-grow
的默认值是 0,这意味着是否膨胀是看情况的。如果我们想让子元素吞噬容器中的多余空间,就需要明确告诉它。
如果多个子元素设置了 flex-grow
,该怎么办?在这种情况下,额外的空间会根据子元素的 flex-grow
值按比例分配给子元素。
我想这样更容易直观地解释清楚。试着递增/递减每个子元素:
第一个子元素想要 1 个单位的额外空间,第二个子元素想要 1 个单位的。也就是说,单位总数是 2 (1 + 1)。每个孩子都能按比例分享额外的空间。
Link to this headingflex-shrink
在我们目前看到的大多数例子中,我们都有额外的空间可以利用。但是,如果我们的子元素对他们的容器来说太大了怎么办?
让我们来测试一下。试着缩小容器,看看会发生什么:
- flex-basis
- 300px
- Actual size:
- 300px
- Reduced by:
- 0%
- flex-basis
- 150px
- Actual size:
- 150px
- Reduced by:
- 0%
有趣吧?两个元素都会缩小,但它们是按比例缩小的。第一个子项的宽度始终是第二个子项宽度的 2 倍
友情提示:flex-basis
的作用与 width
相同。我们使用 flex-basis
是因为它很传统,但如果我们使用 width
,也会得到完全相同的结果!
flex-basis
和 width
设置元素的愿望尺寸。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%
好吧:我们有两个子元素,每个子元素的愿望大小为 250px。容器至少需要 500px 宽,才能容纳假设大小的这两个子元素。
假设我们把容器缩小到 400px。那么,我们就无法将 500px 的内容塞进 400px 的容器中!我们有 100px 的缺口。我们的元素总共需要让出 100px 的空间才能容纳。
这个 flex-shrink
属性可让我们决定如何分配剩余空间。
和 flex-grow
一样,它也是一个比率。默认情况下,两个子元素值都是 flex-shrink: 1
,因此每个子元素都让出空间的 1/2。他们各自放弃 50px,实际大小从 250px 缩小到 200px。
现在,让我们假设第一个子元素值增加到 flex-shrink: 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 图标和图形时发现这种情况。让我们来看一个简化的例子:
当容器变窄时,我们的两个圆形就会被挤压成毛茸茸的椭圆形。如果我们想让它们保持圆形呢?
我们可以通过设置 flex-shrink: 0
来实现:
当我们将 flex-shrink
为 0 时,我们基本上就完全“退出 ”了收缩过程。Flexbox 算法会将 flex-basis
(或 width
) 视为硬性的最小限制。
如果你好奇,这里有这个演示的完整代码:
Flex Shrink ball demo
Result
Link to this heading最小尺寸难题
在这里,我们还需要谈一件事,而且这件事超级重要。它可能是整篇文章中最有帮助的一件事!
假设我们要为一家电子商务商店创建一个流体搜索表单:
当容器缩小到某一点以下时, 内容就会溢出!
可是为什么?? flex-shrink
的缺省值是 1
,我们也没有删除它,所以搜索输入应该可以根据需要缩小!为什么它拒绝缩小?
情况是这样的:除了愿望尺寸外,Flexbox 算法还关心另一个重要尺寸 最小尺寸。
Flexbox 算法拒绝将子元素缩小到最小尺寸以下。 无论我们将 flex-shrink
调到多高,内容都会溢出,而不会进一步缩小!
文本输入框的默认最小尺寸为 170px-200px(因浏览器而异)。这就是我们遇到的限制。
在其他情况下,限制因素可能是元素的内容。例如,试试调整这个容器的大小:
对于包含文本的元素,最小宽度为最长的不可断开字符串的长度。
好消息是:我们可以使用 min-width
属性重新定义最小尺寸。
通过直接在 Flex 子元素上设置 min-width: 0px
,我们可以告诉 Flexbox 算法覆盖“内置”的最小宽度。因为我们将其设置为 0px,所以元素可以根据需要缩小。
同样的技巧在使用 min-height
属性的 Flex 列中也能奏效(不过这个问题似乎不常出现)。
近年来,Flexbox 对编程质量的最大改进之一就是 gap
属性:
gap
允许我们在每个 Flex 子项之间创建空隙。这非常适合导航标题等内容:
gap
是 Flexbox 语言的一个相对较新的新增功能,但自 2021 年初以来,它已在所有现代浏览器中实现。
我还想分享一个与空间相关的技巧。它在 Flexbox 早期就已经存在了,但相对来说比较隐晦,我第一次发现它时大吃一惊。
这个 margin
属性用于在特定元素周围添加空间。在某些布局模式下,如 “流”(Flow)和 “定位”(Positioned),它甚至可以用来将元素居中,即 margin: auto
“
在 Flexbox 中,自动边距要有趣得多:
前面,我们看到了 flex-grow
属性如何吞噬多余空间,并将其应用于子元素。
自动边距 会吞噬多余的空间,并将其应用到元素的边距中。 这样我们就能精确控制多余空间的分布位置。
常见的 header 布局是一边是logo,另一边是一些导航链接。下面我们就来看看如何使用自动页边距构建这种布局:
Code Playground
Result
这个 Corpatech logo 是列表中的第一个列表项。通过 margin-right: auto
,我们收集了所有多余的空间,并将其强制放在第一项和第二项之间。
我们可以使用浏览器开发工具查看这里发生了什么:
我们还有很多其他方法可以解决这个问题:我们可以将导航链接分组到自己的 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
大多数情况下,当我们在二维空间中工作时,我们会希望使用 CSS Grid,但 Flexbox + flex-wrap
绝对有它的用武之地!这个例子展示了 “解构薄饼” 布局,在中型屏幕上,3 个项目堆叠成一个倒金字塔。
当我们设置了 flex-wrap: wrap
时, 子元素就不会缩小到愿望尺寸以下。至少,在可以选择换行/换列的情况下不会!
但是,等等!等等!我们的烤肉串/腊肠的比喻是什么?
有了 flex-wrap: wrap
,我们就不再需要一条主轴线来串联每个项目了。实际上, 每一行都是自己的小型柔性容器。每一行都有自己的串行线,而不是一个大串行线:
在缩小的范围内,我们迄今为止学到的所有规则仍然适用。例如, justify-content
将在每根棍子上分配两个肉串。
但是...现在我们有了多行,如何使用 align-items
呢?现在纵轴可以与多个元素相交!
花点时间考虑一下。如果我们改变这个属性,你认为会发生什么?一旦你有了答案(或至少有了一个想法),看看它是否正确:
每一行都是自己的迷你 Flexbox 环境。align-items
将在环绕每一行的隐形框内上下移动每个项目。
但如果我们想对齐行本身呢?我们可以使用 align-content
属性来实现:
总结一下这里发生的事情:
flex-wrap: wrap
提供了两行内容。- 在每一行中,
align-items
可以让我们上下滑动每个子元素 - 然而,将视图缩小后,我们就可以在一个 Flex 上下文中看到这两行!现在,纵轴将与两行相交,而不是一行。因此,我们不能单独移动这两行,而需要将它们 作为一组进行分配。
- 根据上面的定义,我们处理的是内容,而不是项目。但我们仍在讨论纵轴!因此,我们需要的属性是
align-content
。
因此,我想承认一点:这是一篇内容繁杂的教程。我们已经深入了兔子洞,除非你已经是 Flexbox 专家,否则我估计你会有点头晕。 😅
就像 CSS 中的很多内容一样,Flexbox 在刚开始使用时可能看起来很简单,但当你掌握了基础知识后,其复杂性就会迅速增加。
因此,我们中的许多人在使用 CSS 时很早就陷入了困境。我们知道足够多的知识来完成工作,但却一直在苦苦挣扎。这门语言给人的感觉是摇摇欲坠、难以捉摸,就像一座古老的索桥,随时都可能断裂。一旦出现问题,我们就会在 StackOverflow 上随意搜索一些片段,希望能有所帮助。
这一点都不好玩。这很糟糕,因为 CSS 是大多数前端开发工作的重要组成部分!
事实上,CSS 是一种非常强大和一致的语言。问题在于,我们的大部分心智模型都不完整、不准确。当我们花时间建立起对这门语言的正确直觉时,一切就会变得顺理成章,CSS 的 使用也会变得轻松愉快。. ✨
Link to this heading红包:解密演示
在本教程的开头,我们看到了以下 “4 种布局只需 1 种设置”的演示:
既然我们已经了解了 Flexbox 算法的全部内容,那么您能找出它是如何工作的吗?请随意尝试这里的代码:
Code Playground
Result
感谢您的阅读!这篇博文耗费了我大量的心血,我很高兴能将它公之于众!希望对你有用 💖
Last Updated
June 27th, 2023