前言
Flex
想必大家都很熟悉,也是大家平时在进行页面布局的首选方案。(反正我是!)。不知道大家平时在遇到Flex
布局属性问题时,是如何查阅并解决的。反正,我每次记不住哪些属性或者对哪些属性的用法忘记时。我总是求助于阮一峰
老师写的Flex 布局教程:语法篇[1]。
其实,对于CSS
来讲,大家都抱着一种「死记硬背」的东西来对待它。久而久之,就会出现上述我说的问题,一个属性或者一个使用案例,需要去指定的网站去查询。这算是好的呢,有些同学没有自己的知识体系或者收藏资料。 每次遇到问题,都是baidu/google
一下,然后CV
大发一通。
其实,我们应该把将 CSS 视为一组布局模式。每种布局模式都是一个可以实现或重新定义每个 CSS 属性的「算法」。我们使用 CSS 声明(键/值对)提供算法,算法决定如何使用它们。
换句话说,我们编写的 CSS 是这些算法的输入,就像传递给函数的参数一样。如果我们想真正熟悉 CSS,仅仅学习属性是不够的;我们必须学习算法如何使用这些属性。
只有,我们在对一些布局模式有了一定的掌握之后,我们才会在遇到类似的问题,游刃有余的处理问题。或者说像调用函数一样,输入特定的参数,得到特定的结果。
所以,今天我们来换一种对Flex
的思考角度,对它来一次深度解析。
还有一点,需要说明,下文中不会设计到特有属性的介绍,并且还需要大家对Flex
布局有一点的知识储备。
比方说,下图中标注的一些概念下文中就不会过多介绍了。推荐大家先把阮老师的那个文章通读几遍,对Flex
有一个大体的了解在阅读下文。
好了,天不早了,干点正事哇。
我们能所学到的知识点
❝
1. 前置知识点
❝「前置知识点」,只是做一个概念的介绍,不会做深度解释。因为,这些概念在下面文章中会有出现,为了让行文更加的顺畅,所以将本该在文内的概念解释放到前面来。「如果大家对这些概念熟悉,可以直接忽略」
同时,由于阅读我文章的群体有很多,所以有些知识点可能「我视之若珍宝,尔视只如草芥,弃之如敝履」。以下知识点,请「酌情使用」。❞
CSS 布局算法
CSS 有不同的模式,确定它如何在页面上布局元素。这些模式通常被称为布局算法
或布局模式
。
在 CSS 中有七种布局模式,下图是MDN_CSS_Layout_Mode[2]的描述
其中Multi-column layout
估计大家没咋接触过,剩余的或多或少在我们平时开发中都有接触过。
其中四种被使用最多。流动
、定位
、flex
和grid
。
流动布局(Flow Layout)
默认情况下,CSS 使用所谓的流动布局算法(也称Normal flow
)。流动将页面上的每个元素都视为属于文本文档
。
块级元素
以垂直方式在页面上重叠显示。它们会尽量占用尽可能多的水平空间,同时尽量减少垂直空间的占用。
内联元素
在水平方向上像段落中的文本一样显示在一起。它们通常具有固定的宽度和高度,这就是为什么许多其他我们可能想要使用的属性在这些元素上不起作用的原因。我们可以通过将它们的显示属性更改为inline-block
来更改此行为。
定位布局
如果在元素上使用 position
属性,我们现在正在要求 CSS
根据定位布局算法显示该元素。在此布局模式中,我们可以请求几种不同类型的行为:
绝对定位元素往往因为在其他地方无法正常工作而被认为是一种hacky
的解决方案。
还有一点需要注意,根据我们使用的值的不同,我们可能需要「考虑元素的父级」。例如,在绝对定位元素中,该元素相对于其最近的定位布局祖先定位。这意味着 CSS
将查找 HTML 树
并找到最近的一个祖先,「该祖先也使用了这些值之一」。如果找不到,则绝对定位元素将相对于视口定位。
弹性盒布局
当 display
属性设置为 flex
时,元素将根据弹性盒布局算法布置其子元素。
而它就是我们今天要讲的重点,下文中有更多的介绍。
如果想了解更多的Flex
的细节,可以参考w3c_flexbox[3]。
网格布局
网格与弹性盒类似,只要在元素上使用了 display: grid
,就会开始使用网格布局算法。此布局算法将根据网格布局算法显示所有子元素。
Grid
和 Flexbox
的区别在于,Grid
适用于布局具有列和行的二维内容,而 Flexbox
适用于布局具有「一维内容」,即单个列或行。
详情可以参见这篇针对Grid
的文章《CSS Grid 那些鲜为人知的内幕》。
替换元素
在 CSS 中,替换元素
(Replaced Element
)是指一个由浏览器根据元素的标签和属性创建的、在渲染时展示的元素,而「不是由文档中的内容决定其显示的元素」。这些元素通常是具有外部资源(如图像或嵌入式框架)的元素,其内容由浏览器根据其属性和上下文动态生成。
以下是一些常见的替换元素:
「<img> 元素:」 通过 src
属性引用外部图像。
<img src="image.jpg" alt="Description" />
「<audio> 和 <video> 元素:」 通过 src
属性引用外部音频或视频文件。
<audio controls>
<source src="audio.mp3" type="audio/mp3" />
</audio>
<video width="320" height="240" controls>
<source src="movie.mp4" type="video/mp4" />
</video>
「<iframe> 元素:」 通过 src
属性引用外部网页或嵌入式内容。
<iframe src="https://example.com"></iframe>
「<object> 元素:」 用于嵌入外部资源,如 Flash 动画。
<object data="flash.swf" type="application/x-shockwave-flash">
<!-- fallback content or alternate content -->
</object>
「<canvas> 元素:」 通过 JavaScript 绘制图形。
<canvas width="200" height="200"></canvas>
❝替换元素与非替换元素的主要区别在于,替换元素的渲染不依赖于文档的其他部分。它们的外观和尺寸通常由其属性和外部资源决定。替换元素具有一定的固有尺寸,不受文本或子元素的影响。❞
在 CSS
中,替换元素还可以通过 object-fit
和 object-position
这样的属性进行进一步控制,以指定元素的替换内容的显示方式。例如:
img {
object-fit: cover; /* 图片按比例缩放并覆盖整个容器 */
object-position: center; /* 图片在容器中居中显示 */
}
2. Flexbox 是个啥?
CSS 由许多不同的布局算法组成,官方称之为布局模式
。「每种布局模式都是 CSS 中的一种小型子语言」。默认布局模式是流式布局
,但我们可以通过更改父容器上的display
属性来选择使用Flexbox
:
当我们将 display 设置为 flex 时,我们创建了一个flex格式化上下文
。这意味着,默认情况下,「所有子元素将根据 Flexbox 布局算法定位」。
每种布局算法都是为解决特定问题而设计的。默认的Flow布局
旨在创建数字文档;它本质上是Microsoft Word
的布局算法。「标题和段落以块的形式垂直堆叠,而文本、链接和图像等元素则不显眼地位于这些块内部」。
Flexbox
专注于在行或列中排列一组项目,并提供对这些项目的分布和对齐具有极大控制权。正如其名称所示,Flexbox
关注的是灵活性。我们可以控制项目是增长还是收缩,额外空间如何分配等。
3. Flex Direction
如前所述,Flexbox
的关键在于「控制在行或列中元素的分布」。默认情况下,项目将在「一行中侧边堆叠」,但我们可以通过使用flex-direction
属性切换到列:
使用flex-direction: row
时,「主轴水平运行,从左到右」。当我们切换到flex-direction: column
时,「主轴垂直运行,从上到下」。
❝在Flexbox
中,一切都「基于主轴」。算法不关心垂直/水平,甚至不关心行/列。所有规则都围绕这个主轴以及垂直运行的交叉轴结构。❞
我们可以轻松切换水平布局到垂直布局。所有规则都会「自动适应」。这个特性是 Flexbox 布局模式独有的。
❝子元素将「默认」根据以下两个规则定位:
- 主轴(
Primary Axis
):子元素将「紧密」排列在容器的「起始位置」。 - 交叉轴(
Cross Axis
):子元素将「伸展」以「填充整个容器」。
❝在Flexbox
中,我们决定主轴是水平运行还是垂直运行。这是「所有 Flexbox 计算的基准」。❞
4. 对齐(Alignment)
我们可以使用justify-content
属性来改变「子元素沿主轴」的分布方式:
<<< 左右滑动见更多 >>>
❝由于主轴是row
和column
的情况很类似,下文中我们都按主轴为row
来讲解❞
当涉及到主轴时,我们通常不考虑对齐单个子元素。相反,重点是关于整个组的分布。
我们可以将所有项目紧密堆叠在特定位置(使用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
是一种语法糖,是一种方便的简写,可以「一次性自动设置所有子元素的对齐方式」。❞
Content VS items
在 Flexbox
中,项目沿着主轴分布。「默认情况下,它们很好地排列在一起,侧边相邻」。我可以画一条直线,将所有子元素串起来,就像烤肉一样:
然而,交叉轴是不同的。「一条垂直的直线只会与其中一个子元素相交」。
这更像是垂直方向用牙签串的烤肠,而不是烤肉串:
这里有一个显著的区别。对于烤肠而言,「每个项目都可以沿着它的棍子移动,而不会干扰其他项目」:
相比之下,通过我们的主轴串联每个兄弟元素,一个单独的项目如果要移动位置,那势必会影响周围兄弟元素的。
❝这是主轴和交叉轴之间的基本区别。当我们讨论交叉轴上的对齐时,每个项目都可以随心所欲。然而,在主轴上,我们「只能考虑如何分配整个组」。❞
针对上面的内容,我们可以给出一个正确的定义:
content
— 「一组」可以被分配的“东西”。
因此:我们有justify-content
来控制沿主轴分配整个组,我们有align-items
来沿交叉轴单独定位每个项目。这是我们用来管理 Flexbox
布局的两个主要属性。
当涉及到主轴时,我们必须将项目视为一个组,作为可以分配的内容。
5. 假设大小(Hypothetical size)
假设我有以下的 CSS:
.item {
width: 2000px;
}
我们第一直觉就是「我们将得到一个宽度为 2000 像素的项目」。其实这句话是不对的!
让我们用一个例子来说明。
<style>
.flex-wrapper {
display: flex;
}
.item {
width: 2000px;
}
</style>
<div class="item"></div>
<div class="flex-wrapper">
<div class="item"></div>
</div>
结果缺不一样。
两个项目都应用了完全相同的 CSS。它们都有width: 2000px
。然而,第一个项目比第二个项目宽得多!
差异在于「布局模式」。第一个项目是使用流式布局
(flow
)渲染的,在流式布局中,width
是一个「硬性约束」。当我们设置width: 2000px
时,我们肯定能到一个宽度为 2000
像素的元素,即使它已经超过当前视口的宽度。
❝然而,在 Flexbox
中,width
属性的实现方式不同。这「更像是一个建议而不是硬性约束」。
❞
规范对此有一个名字:「假设大小」(Hypothetical size
)。
在这种情况下,限制因素是父元素
没有足够的空间容纳一个宽度为 2000px
的子元素。因此,子元素的大小被缩小,以「适应空间」。
这是 Flexbox
哲学的核心部分。「事物是流动和灵活的,可以根据世界的限制进行调整」。
6. 增长(Grow
)和萎缩(Shrink
)
要真正了解 Flexbox
的流动性,我们需要讨论三个属性:flex-grow
、flex-shrink
和flex-basis
。
flex-basis
❝在 Flex行
中,flex-basis
的作用与width
相同。在 Flex 列
中,flex-basis
的作用与height
相同。
❞
「Flexbox
中的一切都与主/交叉轴有关」。例如,justify-content
将沿主轴分布子元素,无论主轴是水平还是垂直,它的工作方式都完全相同。
❝然而,width
和height
不遵循此规则!width
「始终会影响水平尺寸」。当我们将flex-direction
从row
切换到column
时,它不会突然变成height
。
❞
因此,Flexbox
创建了一个通用的“大小”属性,称为flex-basis
。它就像width
或height
,但与其他所有属性一样,「与主轴相关联」。它允许我们设置元素在主轴方向上的假设大小
,无论这是水平还是垂直。
下图集中,每个子元素都被赋予了flex-basis: 50px
,但可以调整第一个子元素的flex-basis
。
<<< 左右滑动见更多 >>>
就像我们在width
中看到的那样,flex-basis
更像「是一个建议而不是一个硬性约束」。在某个时候,所有元素都没有足够的空间来保持它们被分配的大小,因此「它们必须妥协,以避免溢出」。
❝一般来说,在 Flex 行
中,我们可以互换使用width
和flex-basis
,但也有一些例外情况。例如,width
属性对替换元素(如图像)的影响与flex-basis
不同。此外,width
可以将项目减小到其最小尺寸
以下,而flex-basis
则不能。❞
flex-grow
默认情况下,Flex 上下文
中的元素将缩小
到它们在主轴上的「最小舒适尺寸」。这通常「会创建额外的空间」。
我们可以使用flex-grow
属性指定如何使用该空间:
<<< 左右滑动见更多 >>>
flex-grow
的「默认值是 0」,这意味着增长是可选的
。如果我们希望「子元素吞并容器中的任何额外空间」,我们需要明确告诉它。
如果多个子元素设置了flex-grow
怎么办?在这种情况下,「额外的空间将根据它们的flex-grow
值成比例地分配给子元素」。
<<< 左右滑动见更多 >>>
❝当单个子元素被赋予正的flex-grow
值时,它将「吞并所有额外的空间」。在这种情况下,数字是无关紧要的:1
和 1000
具有相同的效果。
❞
flex-shrink
在我们迄今为止看到的大多数示例中,我们有额外的空间可以使用。如果我们的子元素太大而父容器无法容纳怎么办?
<<< 左右滑动见更多 >>>
两个项目都会收缩,但它们会「按比例收缩」。第一个子元素始终是第二个子元素宽度的 2 倍。
flex-basis
和width
设置了元素的假设大小
。Flexbox算法
可能会「将元素收缩到低于这个期望大小」,但「默认情况下,它们将始终按比例缩放,保持两个元素之间的比例」。
如果我们不希望元素按比例缩小,可以使用flex-shrink
属性。
<<< 左右滑动见更多 >>>
现在我们有两个子元素,每个都有一个假设大小
为 250px
。容器至少需要 500px
宽度,以便将这些子元素以其假设大小容纳其中。
假设我们将容器缩小到 400px
。嗯,我们不能把 500px
的内容塞进一个 400px
的袋子里!我们有 100px
的亏空。为了使它们适应,我们的元素将需要放弃总共 100px
。
flex-shrink
属性让我们决定如何处理这个亏空。
与flex-grow
类似,它是一个比例。「默认情况下,两个子元素的flex-shrink
都是 1,因此每个子元素消化亏空的一半」。它们各自放弃 50px
,它们的实际大小从 250px
缩小到 200px
。
现在,假设我们将第一个子元素提高到flex-shrink: 3
:
我们总的亏空是 100px
。通常,每个子元素将支付 1/2
,但由于我们已经调整了flex-shrink
,第一个元素最终支付了 3/4
(75px
),第二个元素支付了 1/4
(25px
)。
❝「绝对值并不重要,一切都取决于比例」。如果两个子元素都具有flex-shrink: 1
,每个子元素将支付总亏空的 1/2
。如果两个子元素都增加到flex-shrink: 1000
,每个子元素将支付总亏空的 1000/2000
。无论如何,最终效果都是相同的。
❞
对flex-shrink
:我们可以将其视为flex-grow
的“反面”。它们是同一硬币的两面:
flex-grow
控制当项目小于其容器时额外空间的「分配方式」。flex-shrink
控制项目大于其容器时空间的「移除方式」。
这意味着这两个属性中只能有一个生效。如果有额外的空间,flex-shrink
没有影响,因为项目不需要缩小。如果子元素太大而无法容纳,flex-grow
没有影响,因为没有额外的空间可分配。
防止缩小
有时,我们不希望 Flex
子元素缩小。
让我们看一个例子:
当容器变窄时,我们的两个圆形被挤变形了。如果我们希望它们保持圆形怎么办?
我们可以通过设置flex-shrink: 0
来实现:
<<< 左右滑动见更多 >>>
当我们将flex-shrink
设置为 0 时,实质上我们「完全退出了缩小过程」。Flexbox 算法
将flex-basis
(或width
)视为硬最小限制。
7. 最小尺寸的陷阱
假设我们正在构建一个搜索表单:
当容器缩小到一定程度以下时,内容溢出!
「根本原因是flex-shrink
的默认值是 1」,我们在示例中设置了该属性,按道理输入框应该能够缩小到它需要的程度!但是却事与愿违。
原因是:除了假设大小
之外,Flexbox 算法
还关心另一个重要的大小:「最小大小」。
❝Flexbox算法
拒绝将子元素缩小到其最小大小
以下。无论我们如何增加flex-shrink
,内容将溢出而不是继续缩小!
❞
文本输入框的默认最小大小为 170px-200px
(在不同的浏览器之间有所变化)。
在其他情况下,限制因素可能是元素的内容
。
<<< 左右滑动见更多 >>>
❝对于包含文本的元素,最小宽度是最长不可断开的字符串的长度。❞
好消息是:我们可以「使用min-width
属性重新定义最小大小」。
通过直接在 Flex 子元素
上设置min-width: 0px
,我们告诉 Flexbox
算法覆盖内置
的最小宽度。因为我们将其设置为 0px
,所以元素可以缩小到必要的程度。
8. 间距
❝gap
允许我们在每个 Flex 子元素之间创建空间。❞
这对于诸如导航标题之类的东西非常有用:
自动边距
margin
属性用于在特定元素周围添加空间。在某些布局模式中,如 Flow
和Positioned
(前面都有过介绍),它甚至可以用于通过margin: auto
将元素居中。
在 Flexbox
中,自动边距
变得更加有趣:
<<< 左右滑动见更多 >>>
「自动边距将吞噬额外的空间,并将其应用于元素的边距」。它使我们能够精确控制在哪里分配额外的空间。
一个常见的页眉布局特点是在一侧放置标志,而在另一侧放置一些导航链接。
<style>
ul {
display: flex;
gap: 14px;
}
li.logo {
margin-right: auto;
}
</style>
<nav>
<ul>
<li class="logo">
<a href="/"> 首页 </a>
</li>
<li>
<a href=""> 语言 </a>
</li>
<li>
<a href=""> 个人中心 </a>
</li>
</ul>
</nav>
ul {
list-style-type: none;
}
ul a {
text-decoration: none;
}
列表中的第一项通过给它设置margin-right: auto
,我们「聚集了所有额外的空间,并强制将其放在第一项和第二项之间」。
使用浏览器devtool
来查看元素信息。
9. 包裹
到目前为止,我们的所有项目都是并排或纵列的。flex-wrap
属性允许我们改变这一点。
如果容器宽度不能包含子元素的话,子元素会被隐藏。
我们可以通过设置flex-wrap:wrap
来让子元素自动换行。
<<< 左右滑动见更多 >>>
当我们设置flex-wrap: wrap
时,项目不会收缩到其假设大小
以下。
❝使用flex-wrap: wrap
,我们「不再有一个可以穿过每个项目的单一主轴线」。实际上,「每一行都充当其自己的小型 Flex 容器」。
❞
当我们有多行时,交叉轴现在可能与多个项目相交!
<<< 左右滑动见更多 >>>
❝每一行都是其自己的小型 Flexbox 环境。align-items
将在包围每一行的无形框内上下移动每个项目。
❞
但如果我们想对齐行本身怎么办?我们可以使用align-content
属性:
<<< 左右滑动见更多 >>>
总结一下这里发生的情况:
- 在每一行内,
align-items
允许我们将每个单独的子项上下滑动。 - 然而,在整体上,我们有两行在一个单一的 Flex 上下文内!现在,交叉轴将与两行相交,而不是一行。因此,我们不能单独移动行,我们需要将它们作为一个组进行分配。
- 使用我们上面的定义,我们正在处理内容,而不是项目。但我们仍然在谈论交叉轴!因此,我们想要的属性是
align-content
。
后记
「分享是一种态度」。
「全文完,既然看到这里了,如果觉得不错,随手点个赞和“在看”吧。」
Reference
[1]Flex 布局教程:语法篇:https://www.ruanyifeng.com/blog/2015/07/flex-grammar.html
[2]MDN_CSS_Layout_Mode:https://developer.mozilla.org/en-US/docs/Web/CSS/Layout_mode
[3]w3c_flexbox:https://www.w3.org/TR/css-flexbox-1/
该文章在 2024/5/9 12:40:28 编辑过