曾经,我天真地认为,浏览过即等于掌握;然而,职场多年的洗礼后,我才深刻领悟到这一认知的浅薄。那些一闪而过的知识碎片,如细沙般从指缝滑落,待到真正需要之时,却寻觅无踪,宛如海市蜃楼,令人怅然若失。
形成了时刻笔记的习性,就像拥有了一个忠实的旅伴,在漫漫人生路上,它记录下每一次心跳,每一缕微笑,不让任何美好稍纵即逝。这是一种对生命的热爱,对细节的关注,让平淡生活因记录而精彩,因回忆而丰满。
废话结束,干货来了!
Flexbox 是一种非常强大的布局模式。当我们真正了解它是如何工作的时,我们可以构建自动响应的动态布局,并根据需要重新排列。
一、先看要实现的效果
不废话,直接上干货
下面是相关的css
form {
display: flex;
align-items: flex-end;
flex-wrap: wrap;
gap: 16px;
}
.name {
flex-grow: 1;
flex-basis: 160px;
}
.email {
flex-grow: 3;
flex-basis: 200px;
}
button {
flex-grow: 1;
flex-basis: 80px;
}
完整的例子
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="renderer" content="webkit" />
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
<meta
name="viewport"
content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no"
/>
<title>详解Flexbox布局</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
.flex1 {
padding: 10px;
background: #1a1b1b;
}
form {
display: flex;
align-items: flex-end;
flex-wrap: wrap;
gap: 16px;
padding: 8px;
border: 1px solid #ccc;
border-radius: 5px;
color: #fff;
width: 600px;
margin: 0 auto;
}
.name {
flex-grow: 1;
flex-basis: 160px;
}
.email {
flex-grow: 3;
flex-basis: 200px;
}
button {
flex-grow: 1;
flex-basis: 80px;
}
input {
width: 100%;
}
</style>
</head>
<body>
<div class="flex1">
<div style="color: #fff;width: 600px;margin: 0 auto;">
<div>
当前form的宽度:<span id="rangeWidth">600</span>
</div>
<input type="range" min="200" max="600" value="600" class="slider" id="myRange">
</div>
<form id="demo">
<label class="name" for="name">
姓名:<input type="text" id="name" />
</label>
<label class="email" for="email">
邮箱:<input type="text" id="email" />
</label>
<button>提交</button>
</form>
</div>
<script>
window.onload = function () {
const slider = document.getElementById('myRange');
const output = document.getElementById('demo');
const rangeWidth = document.getElementById('rangeWidth');
slider.oninput = function() {
output.style.width = this.value + 'px';
rangeWidth.innerHTML = this.value;
}
}
</script>
</body>
</html>
我记得以前遇到这样的需求时,我完全感到困惑。当我学习了Flexbox 的基础知识后,这绝对是魔法 ,太神奇了!
在接下来的文中,我将继续分享详细的 Flexbox 知识。我们会通过每一个属性来学习 Flexbox 是如何工作的。无论你是CSS的初学者,还是已经使用Flexbox多年的人,我敢打赌你会学到很多东西!
让我们开始吧!
二、Flexbox 简介
CSS由许多不同的布局算法组成,官方称为“布局模式”。每种布局模式都是CSS中自己的子语言。默认的布局模式是 Flow 布局,但我们可以通过更改父容器上的display为 Flexbox。
当我们将 display
设置为 flex
时,我们创建了一个“flex formatting context”。这意味着,默认情况下,所有子项都将根据 Flexbox 布局算法进行定位。
每种布局算法都旨在解决特定问题。默认的“流”布局旨在创建流文档;它本质上是Microsoft Word布局算法。标题和段落作为块垂直堆叠,而文本、链接和图像等内容则不显眼地位于这些块中。
那么,Flexbox解决了什么问题呢?Flexbox 就是将一组项目排列成一行或一列,并让我们对这些项目的分布和对齐进行精准的控制。顾名思义,Flexbox 是关于灵活性的。我们可以控制内容是增长还是缩小,额外空间的分配方式等等。
你可能想知道:既然 CSS Grid 在现代浏览器中得到了很好的支持,那么 Flexbox 不是已经过时了吗?
CSS Grid 是一种很棒的布局模式,但它解决的问题与 Flexbox 不同。我们应该学习这两种布局模式,并使用正确的工具来完成这项工作。
Flexbox 在动态、流畅的 UI 中以垂直或水平列表排列项目时仍然占据主导地位。我们将在本文中看到一个示例,这是使用 CSS Grid 无法轻松完成的。
作为一个对 CSS Grid 和 Flexbox 都感到满意的人,我仍然发现自己经常接触 Flexbox!
三、Flex direction 布局方向
如上所述,Flexbox 就是控制一行或一列中元素的分布。默认情况下,项将并排堆叠成一行,但我们可以控制是行还是列显示。
flex-direction: row
,主轴从左到右水平延伸,块内的子项从左到右排列。
flex-direction: column
,主轴从上到下垂直运行,块内的子项从上到下排列。
先看效果:
在 Flexbox 中,一切都基于主轴。该算法不关心垂直/水平,甚至不关心行/列。所有规则都是围绕这个主轴和垂直运行的十字轴构建的。
当我们学习了 Flexbox 的规则时,我们可以从水平布局无缝切换到垂直布局。所有规则都会自动适应。此功能是 Flexbox 布局模式所独有的。
下图是这些规则的快速可视化效果:
在 Flexbox 中,我们决定主轴是水平运行还是垂直运行。这是所有 Flexbox 计算都与此挂钩的根。
四、Flex direction 对齐方式
我们可以使用属性 justify-content
更改子项沿主轴的分布布局方式。
当涉及到主轴时,我们通常不会从对齐单个子项的角度来考虑。相反,这完全与组的分布有关。
我们可以将所有项目集中在特定位置(有 flex-start
、 center
和 flex-end
),或者我们可以将它们分开(带有 space-between
、 space-around
和 space-evenly
)。
先看下图(gif录制软件略有瑕疵):
对于十字轴,垂直方向,情况略有不同。我们使用该属性align-items
如图:
是不是挺有趣的,在align-items
中,我们有一些与 justify-content
相同的选项,但没有完全重叠,如下图。
为什么他们不共享相同的选项?我们很快就会解开这个谜团,首先,我先分享另一个对齐属性: align-self
。
与 align-items
和justify-content不同
, align-self
,它应用于子元素,而不是容器。它允许我们更改特定子项沿十字轴的对齐方式,请看下图:
align-self
具有与 align-items
相同的所有值。事实上,它们改变了完全相同的事情。 align-items
是句法糖,一种方便的速记,可以自动一次在所有孩子上设置对齐方式。
没有 justify-self
。要理解为什么不,我们需要更深入地研究 Flexbox 算法。
五、Content vs items
根据我目前所了解的,Flexbox可能看起来相当随意。为什么是justify-content
和align-items
,而不是justify-items
,或者align-content
?
就此而言,为什么有一个align-self
,但没有justify-self
??
这些问题触及了Flexbox最重要也是最容易被误解的地方。为了更好的理解,我用一个比喻。
在Flexbox中,项目沿主轴分布。默认情况下,它们很好地并排排列在一起。我们可以画一条水平直线穿过全部的孩子,这像不像一个烤肉串?如下图
然而横轴是不同的。一条直的垂直线只会和一个孩子相交。
这不太像烤肉串,而更像一群混合的香肠?看图:
这里有一个显著的不同。有了混合的香肠,每个项目都可以沿着它的签子移动而不干涉其他项目
相比之下,我们沿着主轴移动每个兄弟姐妹,每个都不可以沿着它的签子移动,否则会撞到它的兄弟姐妹!
这就是主轴/横轴之间的根本区别。当我们通过轴对齐时,每一项都可以为所欲为。在这里我们只能考虑如何分配每一项。
这就是为什么没有 justify-self
.这对于中间的部分来说意味着什么justify-self: flex-start
?那里已经有另一块了!
考虑到所有这些情况,让我们给我们一直在谈论的4个术语下一个正确的定义:
-
justify
—沿主坐标轴放置某物. -
align
—沿横轴放置某物. -
content
—一组可以分发的“东西”。 -
items
—可以单独定位的单个项目。
所以:我们有justify-content
来控制群体在主轴上的分布,我们有align-items
沿横轴单独定位每个项目。这是我们用来管理Flexbox布局的两个主要属性。
没有justify-items
因为同样的原因justify-self
;当谈到主轴时,我们必须把这些物品看作一个整体,作为可以分发的内容。
怎么样align-content
?实际上,这个存在于Flexbox中!稍后,我们会谈到flex-wrap特性
。
六、设定尺寸
让我们来谈谈我对Flexbox最大的领悟。
假设我有下面的CSS:
.item {
width: 2000px;
}
正常的话我们看到这个肯定会说:这简单,会得到一个2000像素宽的元素”。但这是真的吗?
让我们来测试一下,先看效果图
参考代码:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>验证设置width=2000px的效果</title>
<style>
.flex-wrapper {
display: flex;
}
.item {
width: 2000px;
height: 200px;
background: #ccc;
border: 1px solid #000;
margin-bottom: 20px;
}
</style>
</head>
<body>
<div class="item">display: block</div>
<div class="flex-wrapper">
<div class="item">display: flex</div>
</div>
</body>
</html>
这是不是很有趣?
这两个项目应用了完全相同的CSS。他们都有width: 2000px
。然而,第一项比第二项宽得多!
不同之处在于布局模式。第一个项目是使用流布局呈现的,在流布局中,width
是一个硬约束。当我们设置width: 2000px时
,我们将得到一个2000像素宽的元素。
然而在Flexbox里,在width
属性的实现方式是不同的。与其说是硬性约束,不如说是建议。
该规范为此命名为假设尺寸。这是一个元素的大小将在一个完美的乌托邦世界里,没有任何阻碍。
事情貌似就是这么简单。在这种情况下,限制因素是父元素没有那么宽的空间能放下一个2000px宽的孩子。所以,孩子的尺寸被缩小了,这样才合适。
这是Flexbox理念的核心部分。事物是流动的,灵活的,可以适应世界的限制。
算法的输入
我们倾向于认为CSS语言是属性的集合,但是我认为这是错误的思维模式。正如我们已经看到的
width
属性的行为因所使用的布局模式而异!相反,我喜欢把CSS看作是布局模式的集合。每种布局模式都是一种算法,可以实现或重新定义每个CSS属性。我们用CSS声明(键/值对)提供一个算法,算法决定如何使用它们。
换句话说,我们编写的CSS是这些算法的输入,就像传递给函数的参数。如果我们想真实地;诚挚地;准确地对CSS感到舒服,光学属性是不够的;我们必须了解算法是如何使用这些属性。
这是我这篇博客的中心思想,
面向JavaScript开发人员的CSS。我不会让你记住一堆难以理解的CSS片段,而是打开这门语言的思路,学习所有布局模式是如何工作的。
七、伸缩属性
我们已经在上面的例子了解了,Flexbox算法具有一些内置的灵活性,包括设定尺寸宽度。实际中为了要了解Flexbox的流动性,我们需要讨论3个属性:flex-grow
, flex-shrink
,以及flex-basis
.
让我们来看看每个属性。
flex弹性基础:flex-basis
我承认:在过去的很长一段时间,我真的不明白flex-basis是干什么的
。😅
简单来说:在柔性行中,flex-basis
与width
做同样的事情。在柔性柱中,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
与其说是硬约束,不如说是建议。在某一点上,没有足够的空间来容纳所有的元素,所以他们不得不妥协,以避免溢出。
一个不确定的事物
一般来说,我们可以使用
width
和flex-basis
在弹性行中可以互换,但也有一些例外。例如,在
width
属性对图像等被替换元素的影响不同于flex-basis
。还有,
width
可以将项目缩小到其以下最低限度尺寸,而flex-basis
不能。这已经超出了这篇博文的范围,但是我想提一下,因为您可能偶尔会遇到这两种属性具有不同效果的边缘情况。
flex灵活增长:flex-grow
默认情况下,Flex上下文中的元素将沿着主轴收缩到它们最小的舒适尺寸。这通常会产生额外的空间。
我们可以指定如何使用该空间flex-grow
属性:
先看动图效果:
flex-grow
的默认值为0,这意味着增长是选择加入的。如果我们想让孩子吃掉容器中任何多余的空间,我们需要明确地告诉他。
如果有多个孩子设置了flex-grow
在这种情况下,额外的空间会根据孩子的flex-grow
价值进行计算。
用文字不太好描述,我觉得还是用实例直观地解释会更容易。我们看下图中尝试增加/减少每个孩子的glex-grow值。
flex收缩:flex-shrink
在我们到目前为止看到的大多数例子中,我们都有额外的工作空间。但是如果我们的孩子长的太壮了,超出了当前空间?
我们来测试一下,当尝试缩小父容器的空间,看看会发生什么?如下动图:
很有趣吧?两个盒子都在变化,但是它们按比例缩小。第一个孩子的宽度总是第二个孩子的两倍。
友情提示:flex-basis
与width
的目的相同。我们将使用flex-basis
因为这是惯例,但是我们会得到完全相同的结果如果我们使用width
!
flex-basis
和width
“设置元素”假设尺寸。Flexbox算法可能会将元素缩小到期望的大小以下,但默认情况下,它们将总是一起缩放,保持两个元素之间的比例。
现在,如果我们不希望我们的元素按比例缩小?那就要用到flex-shrinkl
。
花几分钟时间看看这个演示。看看你能不能搞清楚这是怎么回事。我们将在下面探讨。
如上我们有两个孩子,每个孩子的假设尺寸都是250像素。容器至少要有500像素宽,才能容纳假设大小的这些孩子。
假设我们把容器缩小到400像素。我们不能把500像素的内容塞进一个400像素的袋子里!我们有100像素的超出容器。我们的元素总共需要放弃100像素,才能适配。
这flex-shrink会
让我们决定如何支配差额。
默认情况下,两个孩子都有flex-shrink: 1
,所以每个孩子都会减去50像素。他们每人损失50像素,实际大小从250像素缩小到200像素。
现在,让我们假设我们把第一个孩子摇到flex-shrink: 3
:
我们的总超额为100像素。通常,每个孩子都会减去宽度,但是因为我们设置了flex-shrink
,第一个元素结束支付(78px),第二个元素支付(26px)。
注意,绝对值并不重要,都是比例的问题。如果两个孩子都有flex-shrink: 1
,每个孩子将减去总差额的。如果两个孩子都被拐到flex-shrink: 1000
,每个孩子将减去1000/2000。不管怎样,结果都是一样的。
收缩和比例
在我们看到的例子中,两个Flex子元素都有相同的假设大小(250像素)。当计算出如何缩小它们时,我们可以只使用
flex-shrink
.正如我们前面看到的,收缩算法也会尝试保持兄弟姐妹之间的比例。如果第一个孩子的大小是第二个孩子的两倍,那么它会收缩得更厉害。
所以,这个全部计算包括查看每个孩子的
flex-shrink
和它的相对大小。
不久前我有了一个顿悟flex-shrink
:我们可以把它看作是的“逆”flex-grow
。它们是同一枚硬币的两面:
-
flex-grow
控制如果有额外的空间被分配时,当设置了flex-grow的盒子空间小于剩余空间时会把额外的空间分配到这个盒子里,充满剩余空间。 -
flex-shrink
控制如果空间不够时,当容器里的物品(盒子)大于最外层的容器空间时,各个孩子会自动按照比例分配减少超出的空间。
这意味着这些属性可以同时激活。如果有多余的空间,flex-shrink
没有影响,因为项目不需要收缩。如果孩子们对于他们的容器来说太大了,flex-grow
没有影响,因为没有多余的空间来分配。
我喜欢把它想象成两个独立的领域。你要么在地球上,要么在地球外。每个世界都有自己的规则。
防止收缩
有时候,我们不会想要我们的一些孩子变小了。
对于SVG图标和一些特定的形状,我总是注意到这一点。让我们看一个简化的例子,注意两边的圆形。
当容器变窄时,我们的两个圆被挤压成一个椭圆形。如果我们想让它们保持圆形呢?
我们可以通过设置flex-shrink: 0
:
当我们设置了flex-shrink为
0的时候,这两圆选择退出了收缩过程。Flexbox算法将处理flex-basis
(或者width
)作为硬性的最低限制。
更简单的方法?
所以,我在整理这个概念时,时不时地在考虑,有人肯定会想为什么我们要这么麻烦的使用
flex-shrink会不会
有更简单的方法可用,比如设置.item-round { min-width: 32px;}
在几年前的话,我会同意的。如果我们设置一个最小宽度,这个项目不就可以实现了吗!
我认为这是很容易混淆“熟悉的”和“简单的”的情况之一。你可能会觉得
min-width
比flex-shrink
更好用,但这并不意味着flex-shrink
就更复杂了!经过几年的练习,我真的觉得
flex-shrink: 0
是解决这个特殊问题的更直接的方法。不过,min-width
在Flexbox算法中仍然扮演着重要的角色!我们接下来会谈到这一点。
最小尺寸问题
这里还有一件事需要说一下,超级重要!这可能是整篇文章中唯一最有帮助的东西! 假设我们正在为一个电子商务商店构建一个流动的搜索表单。
当容器收缩到某一宽度以下时,内容就会溢出! 但是为什么呢?flex-shrink的默认值是1,我们还没有删除它,所以搜索输入应该能够根据需要缩小!为什么它拒绝收缩?
事情是这样的:除了假设的大小,Flexbox算法还关心另一个重要的大小:最小大小。
Flexbox算法拒绝将子项缩小到最小尺寸以下。内容将溢出而不是进一步收缩,无论我们将伸缩曲柄设置得多高! 文本输入的默认最小尺寸为170px-200px(因浏览器而异)。
这就是我们在上面遇到的限制。 在其他情况下,限制因素可能是元素的内容。例如,尝试调整此容器的大小。
对于包含文本的元素,最小宽度是最长的单词的长度。 好消息是:我们可以用min-width属性重新定义最小尺寸。
我们给input设置min-width:0
通过直接在Flex子对象上input设置min-width: 0px,我们告诉Flexbox算法覆盖“内置”最小宽度。因为我们已经将它设置为0px,所以元素可以根据需要收缩。 同样的技巧也适用于具有min-height属性的Flex列(尽管这个问题似乎不经常出现)。
警告:小心行事!!!
值得注意的是,内置的最小尺寸确实有其用途。这意味着作为一个保护,以防止更糟糕的事情发生。
当我们将min-width: 0px应用于包含文本的Flex子对象时,事情会变得更糟!如下图:
强大的能力意味着巨大的责任,对于Flexbox来说,最小宽度是一个特别强大的特性。它不止一次让我摆脱困境,但我总是小心翼翼地确保我没有让事情变得更糟!
八、gaps 间距
近年来 Flexbox 生活质量最大的改进之一是 gap
属性,曾经为了实现间距还是傻傻的用margin。
gap
允许我们在每个 Flex 子项之间创建空间。这对于导航标头等内容非常有用。
gap 是 Flexbox 语言的一个相对较新的新增功能,自 2021 年初以来,它已在所有现代浏览器中实现。
在flex中使用自动边距margin auto
在分享一个与间距相关的技巧。它从 Flexbox 的早期就已经存在了,但它相对晦涩难懂,当我第一次发现它时,它让我大吃一惊。
margin
属性用于在特定元素周围添加空间。在某些布局模式中,例如 Flow 和 Positioned,它甚至可以用于将元素居中,并使用 margin: auto
。
如果把自动边距放在 Flexbox 中会发生什么呢?我们先看图:
之前,我们看到了 flex-grow
属性如何吞噬任何额外的空间,并将其应用于子元素。
上图我们看到了Auto margins 将吞噬多余的空间,并将其应用于元素的 margin。它使我们能够精确控制在何处分配额外的空间。
常见的标题布局的一侧是 logo,另一侧是一些导航链接。以下是我们如何使用 auto margins 构建此布局:
logo图标是列表中的第一个列表项。通过给它 margin-right: auto
,我们收集了所有额外的空间,并强制它位于第 1 项和第 2 项之间。
我们可以使用浏览器控制台查看这里发生了什么:
还有很多其他方法可以解决这个问题:我们可以将导航链接分组到它们自己的 Flex 容器中,或者我们可以使用 flex-grow 来增加第一个列表项。自从知道了这个方法后,我更喜欢用自动边距解决方案。我们将额外的空间视为一种资源,并决定它应该去哪里。
源码
未完待续.......