CSS——Flexbox布局另类理解

Flexbox是一种非常强大的布局模式。当我们真正理解它们如何工作时,我们可以构建自动响应的动态布局,并且根据需要重新排列元素。

看下面一个例子:

新建一个dynamic-layout.html文件

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <link rel="stylesheet" href="dynamic-layout.css"/>
  <style>
	  form {
		display: flex;
		align-items: flex-end;
		flex-wrap: wrap;
		gap: 8px;
	  }
	  .name {
		flex-grow: 1;
		flex-basis: 120px;
	  }
	  .email {
		flex-grow: 3;
		flex-basis: 170px;
	  }
	  button {
		flex-grow: 1;
		flex-basis: 70px;
	  }
  </style>
  <title>dynamic-layout</title>
</head>
<body>
	<form>
	  <label class="name" for="name-field">
		Name:
		<input id="name-field" />
	  </label>
	  <label class="email" for="email-field">
		Email:
		<input id="email-field" type="email" />
	  </label>
	  <button>
		Submit
	  </button>
	</form>
</body>
</html>

新建一个dynamic-layout.css文件

*, *:before, *:after {
    box-sizing: border-box;
    line-height: 1.5;
    line-height: calc(1em + 0.5rem);
    -webkit-font-smoothing: antialiased;
    font-family: Wotfard;
}

/* Cosmetic styles */
form {
  padding: 8px;
  border: 1px solid hsl(0deg 0% 50%);
}

label {
  font-weight: 500;
}
input {
  display: block;
  width: 100%;
  height: 2.5rem;
  margin-top: 4px;
}
button {
  height: 2.5rem;
}

如果将页面的宽度由大到小不断调整,会依次出现以上四种不同的布局;也许大部分人说,这有啥难的,不就是媒体查询吗?可是你注意到没,在CSS代码中一点媒体查询的痕迹也没有,完全是这个demo的作者出于对Flexbox布局模式的深入理解,今天我们就来学习对Flexbox布局算法的深入理解,这样你也能学会不用媒体查询也能动态布局啦。

在这篇文章中,我想提高您对Flexbox布局的心智模型。通过了解这些属性中的每一个,我们将建立对 Flexbox 算法如何工作的感觉。无论您是 CSS 初学者,还是已经使用 Flexbox 多年,我敢打赌您会学到很多东西!

Flexbox简介

CSS由许多不同的布局算法组成,官方称为“布局模式”。每种布局模式在CSS 中有自己的子语言。默认布局模式是 Flow 布局,但我们可以通过更改父容器上的 display 属性来选择使用 Flexbox:

看下面的例子:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <link rel="stylesheet" href="default-flexbox.css"/>
  <style>
	.wrapper{
		
	}
	.wrapper-item {
		background:hsl(210deg,8%,50%);
		font-size: 1rem;
		margin: 2px;
		padding: 10px 16px;
		transform: none;
		transform-origin: 50% 50% 0px;
		color:#fff;
	}
  </style>
  <title>default-to-flexbox</title>
</head>
<body>
	<div class="wrapper">
		<div class="wrapper-item">hello</div>
		<div class="wrapper-item">to</div>
		<div class="wrapper-item">the</div>
		<div class="wrapper-item">world</div>
	</div>
</body>
</html>

当我们将display设置为 flex 时,我们创建了一个“flex 格式化上下文”。这意味着,默认情况下,所有子项都将根据 Flexbox 布局算法进行定位。

每个布局算法都是为解决特定问题而设计的。默认的“Flow”布局旨在创建数字文档;它本质上是 Microsoft Word 布局算法。标题和段落作为块垂直堆叠,而文本、链接和图像等内容则不显眼地位于这些块中。

那么,Flexbox解决了什么问题呢?Flexbox 就是将一组项目排列成一行或一列,并让我们对这些项目的分布对齐方式进行大量控制。顾名思义,Flexbox 就是关于灵活性的。我们可以控制项目是增长还是收缩额外空间的分配方式等等。

它过时了吗? 

你可能想知道:既然 CSS Grid 在现代浏览器中得到了很好的支持,那么 Flexbox 是不是已经过时了?

CSS Grid 是一种美妙的布局模式,但它解决的问题与 Flexbox 不同。我们应该学习这两种布局模式,并使用正确的工具来完成工作。

当涉及到在垂直或水平列表中排列项目的动态、流畅的 UI 时,Flexbox 仍然占据主导地位。

老实说,作为一个同时熟悉 CSS Grid 和 Flexbox 的人,我仍然发现自己经常接触 Flexbox!

Flex direction

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

使用 flex-direction: row,主轴从左到右水平运行。当我们翻转到 flex-direction: column 时,主轴从上到下垂直运行。

在 Flexbox 中,一切都基于主轴。该算法不关心垂直/水平,甚至行/列。所有的规则都是围绕这个主轴和垂直运行的交叉轴构建的。

这很酷。当我们学习了 Flexbox 的规则后,我们可以从水平布局无缝切换到垂直布局。所有的规则都是围绕这个主轴和垂直运行的交叉轴构建的。

默认情况下,子项目将根据以下 2 条规则定位:

  1. 主轴:子项目将在容器的开头聚集在一起。
  2. 交叉轴:子项目会伸展开来填满整个容器。

看看这个规则的可视化展示

在 Flexbox 中,我们决定主轴是水平运行还是垂直运行。这是所有 Flexbox布局计算都与之关联的基础。

Alignment

我们可以使用 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-items的值是一模一样,一个是对所有元素,一个是对特定子元素。

为什么单个子元素没有justify-self要理解为什么,我们需要更深入地研究 Flexbox 算法。

Content 与 items

根据我们目前所学的内容,Flexbox 似乎看起来非常随意。为什么是 justify-contentalign-items,而不是 justify-itemsalign-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 属性时介绍它。

算法的输入

我们倾向于将 CSS 语言视为属性的集合,但我认为这是错误的思维模式。正如我们上一篇所见,width 属性的行为因所使用的布局模式而异!

相反,我喜欢将 CSS 视为布局模式的集合。每种布局模式都是一种算法,可以实现或重新定义每个 CSS 属性。我们为我们的 CSS 声明(键/值对)提供一个算法,算法决定如何使用它们。

换句话说,我们编写的 CSS 是这些算法的输入,就像传递给函数的参数一样。如果我们想真正对 CSS 感到舒服自在,仅仅学习属性是不够的;我们必须了解算法如何使用这些属性。

Growing and shrinking

我们已经看到 Flexbox 算法具有一些内置的灵活性,具有推荐的大小。但要真正了解 Flexbox 的特点,我们需要讨论 3 个属性:flex-grow、flex-shrink 和 flex-basis

flex-basis

我承认:很长一段时间,我都没有真正理解 flex-basis 到底是怎么回事。 🤗

简单来说:在 Flex 行中,flex-basis 与宽度起相同的作用。在 Flex 列中,flex-basis 与高度起相同的作用。

正如我们所了解的,Flexbox 中的所有内容都与主轴/交叉轴挂钩。例如,justify-content 会沿主轴分布子项,无论主轴水平还是垂直,它的工作方式都完全相同。

但是,宽度和高度不遵循此规则!宽度将始终影响水平尺寸。当我们将 flex-direction 从行翻转到列时,它不会突然变成高度。

因此,Flexbox 作者创建了一个通用的“大小”属性,称为 flex-basis。它就像宽度或高度,但与其他一切一样与主轴挂钩。它允许我们在主轴方向上设置元素的推荐大小,无论是水平还是垂直。

看下面的例子:

就像我们看到的宽度一样,flex-basis 更像是一个建议,而不是硬性约束(黄色区域在flex-basis为250时,实际宽度没有250,而是一个弹性的建议值)。在某个时刻,没有足够的空间让所有元素都按指定的大小放置,因此它们必须妥协以避免溢出。

不完全一样

通常,我们可以在 Flex 行中交替使用 width 和 flex-basis,但也有一些例外。例如,width 属性对图像等替换元素的影响不同于 flex-basis。此外,宽度可以将项目缩小到其最小尺寸以下,而 flex-basis 不能。

flex-grow

默认情况下,Flex 上下文中的元素将沿着主轴缩小到它们的最小舒适尺寸。这通常会产生额外的空间。我们可以指定如何使用 flex-grow 属性消耗该空间:看下图

flex-grow 的默认值为 0,这意味着增长是可选的。如果我们想让孩子吞噬容器中的任何额外空间,我们需要明确地告诉它。

如果多个子项目设置 flex-grow 怎么办?在这种情况下,额外的空间在子项目之间分配,根据他们的 flex-grow 值按比例分配。

flex-shrink

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

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

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

flex-basis 和 width 设置元素的推荐大小。 Flexbox 算法可能会将元素缩小到低于此所需大小,但默认情况下,它们将始终一起缩放,并保持两个元素之间的比例。

现在,如果我们不希望我们的元素按比例缩小怎么办?这就是 flex-shrink 属性的用武之地。

好吧,所以:我们有两个子项目,每个子项目的推荐大小都是 250px。容器需要至少 500 像素宽才能容纳这些处于推荐大小的子项目。

假设我们将容器缩小到 400 像素。好吧,我们不能把 500 像素的内容塞进 400 像素的包里!我们有 100px 的赤字。我们的元素将需要放弃 100px 的总尺寸,以便它们适合。

flex-shrink 属性让我们决定如何支付余额。

就像 flex-grow 一样,它是一个比率。默认情况下,两个子项目都有 flex-shrink: 1,所以每个子项目支付 ½ 的余额。他们每个人放弃 50px,他们的实际大小从 250px 缩小到 200px。

现在,假设我们将第一个子节点设置为 flex-shrink: 3:

我们的总赤字为 100px。通常,每个子项目会支付 ½,但因为我们已经修改了 flex-shrink,所以第一个元素最终支付 ¾ (75px),第二个元素支付 ¼ (25px)。

请注意,绝对值并不重要,重要的是比率。如果两个子项目都有 flex-shrink: 1,每个子项目将支付总赤字的 ½。如果两个子项目都被调高到 flex-shrink: 1000,每个子项目将支付总赤字的 1000/2000。无论哪种方式,结果都是一样的。

收缩和比例

在我们一直在查看的示例中,两个 Flex 子项具有相同的推荐大小 (250px)。在弄清楚如何缩小它们时,我们可以专门使用 flex-shrink 来计算它。

不过,正如我们之前看到的,缩小算法也会尝试保持兄弟姐妹之间的比例。如果第一个孩子是第二个孩子的 2 倍,它会收缩得更厉害。

因此,完整的计算涉及查看每个子项的相对 flex-shrink 及其相对大小。

不久前,我对 flex-shrink 有了一个顿悟:我们可以将其视为 flex-grow 的“逆”。它们是同一枚硬币的两面:

  • flex-grow 控制当项目小于容器时如何分配额外空间。
  • flex-shrink 控制当项目大于容器时如何删除空间。

这意味着一次只能激活其中一个属性。如果有额外的空间,则 flex-shrink 无效,因为项目不需要收缩。如果子项目们对于他们的容器来说太大了, flex-grow 就没有效果,因为没有额外的空间可以分配。

我喜欢将其视为两个独立的领域。你要么在地球上,要么在颠倒的地方?每个世界都有自己的规则。

Preventing shrinking

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

我一直在使用 SVG 图标和形状注意到这一点。让我们看一个简化的例子:

当容器变窄时,我们的两个圆圈会被压扁成椭圆形。如果我们希望它们保持循环怎么办?

我们可以通过设置 flex-shrink: 0 来做到这一点:

当我们将 flex-shrink 设置为 0 时,我们实际上完全“选择退出”收缩过程。 Flexbox 算法会将 flex-basis(或宽度)视为硬性最小限制。

看下面的例子:

新建一个shrink-prevent.html

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <link rel="stylesheet" href="shrink-prevent.css"/>
  <style>
	/* This is the key property: */
	  .item.ball {
		flex-shrink: 0;
	  }
  </style>
  <title>shrink-prevent-flexbox</title>
</head>
<body>
	<div class="wrapper">
	  <div class="item ball"></div>
	  <div class="item stretch"></div>
	  <div class="item ball"></div>
	</div>
</body>
</html>

新建一个shrink-prevent.css

body {
  background: hsl(210deg, 30%, 12%);
  color: hsl(0deg 0% 100%);
}
.wrapper {
  display: flex;
  gap: 8px;
}
.item {
  height: 32px;
  border: 2px solid hsl(210deg 8% 50%);
  background: hsl(210deg 15% 20%);
}
.item.stretch {
  /*
    Because this item is empty, it
    has a default hypothetical width
     of 0px. This isn't realistic,
     though; in a typical case,
     there would be content in here!
     And so we set a width of 300px
     to simulate it containing stuff.
  */
  width: 300px;
  flex-grow: 1;
  border-radius: 16px;
}
.item.ball {
  width: 32px;
  /*
    NOTE: We could prevent the circle
    from squishing into an oval by
    setting border-radius: 16px
    instead. I'm using percentages
    because it makes for a better
    demo. But even with a pixel-based
    radius, we still need
    flex-shrink: 0 to prevent the
    item from shrinking (give it a
    shot and see the difference!).
  */
  border-radius: 50%;
}

相信通过这个例子大家能理解怎么阻止收缩啦!

The minimum size gotcha

我们还需要在这里讨论一件事,这非常重要。这可能是整篇文章中最有用的东西!

假设我们正在为电子商务商店构建一个流畅的搜索表单:

当容器收缩到一定程度以下时,内容就会溢出!

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

事情是这样的:除了推荐的大小,Flexbox 算法还关心另一个重要的大小:最小大小。

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

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

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

对于包含文本的元素,最小宽度是最长牢不可破的字符串的长度。

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

通过直接在 Flex 子元素上设置 min-width: 0px,我们告诉 Flexbox 算法覆盖“内置”最小宽度。因为我们将它设置为 0px,元素可以根据需要缩小。同样的技巧可以在具有 min-height 属性的 Flex 列中使用(尽管问题似乎并不经常出现)。

提醒一句:非必要不要修改这个属性的值,因为可能造成访问性问题。

Gaps

近年来最大的 Flexbox 中改进之一是 gap 属性:

gap 允许我们沿着主轴在每个 Flex 子元素之间创建空间。这对于导航标题之类的东西非常有用:

gap 是 Fl​​exbox 语言的一个相对较新的补充,但自 2021 年初以来,它已在所有现代浏览器中实现。

Auto margins

我想分享另一个与间距相关的技巧。它在 Flexbox 的早期就已经存在,但它相对晦涩难懂,当我第一次发现它时,它让我大吃一惊。

margin 属性用于在特定元素周围添加空间。在某些布局模式中,例如 Flow 和 Positioned,它甚至可以用于使元素居中,margin: auto。

Flexbox 中的Auto margin更有趣:

早些时候,我们看到了 flex-grow 属性如何吞噬任何额外的空间,并将其应用于子项目。自动边距会吞噬额外的空间,并将其应用于元素的边距。它使我们能够精确控制在何处分配额外空间。

常见的标题布局在一侧具有徽标,在另一侧具有一些导航链接。以下是我们如何使用自动边距构建此布局:

新建auto-margin.html

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <link rel="stylesheet" href="auto-margin.css"/>
  <style>
	ul {
		display: flex;
		gap: 12px;
	  }
	  li.logo {
		margin-right: auto;
	  }
  </style>
  <title>auto-margin-flexbox</title>
</head>
<body>
	<nav>
	  <ul>
		<li class="logo">
		  <a href="/">
			Corpatech
		  </a>
		</li>
		<li>
		  <a href="">
			Mission
		  </a>
		</li>
		<li>
		  <a href="">
			Contact
		  </a>
		</li>
	  </ul>
	</nav>
</body>
</html>

新建auto-margin.css

body {
  padding: 0;
}
nav {
  padding: 12px;
  border-bottom: 1px dotted
    hsl(0deg 0% 0% / 0.2);
}
ul {
  list-style-type: none;
  align-items: baseline;
  padding: 0px;
  margin: 0;
}
ul a {
  color: inherit;
  text-decoration: none;
  font-size: 0.875rem;
}
.logo a {
  font-size: 1.125rem;
  font-weight: 500;
}

Corpatech 徽标是列表中的第一个列表项。通过给它 margin-right: auto,我们收集了所有额外的空间,并将其强制放在第一项和第二项之间。

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

Wrapping

到目前为止,我们已经介绍了很多东西。我还想分享一件重要的事情。

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

大多数时候,当我们在二维空间工作时,我们会想要使用 CSS Grid,但是 Flexbox + flex-wrap 肯定有它的用处!这个特殊的例子展示了“解构的煎饼”布局,其中 3 个项目在中型屏幕上堆叠成一个倒金字塔。

当我们设置 flex-wrap: wrap 时,项目不会缩小到低于其假设大小。至少,当换行到下一行/列是一个选项时不会!

可是等等!我们的烤肉串/鸡尾酒香肠比喻怎么样?

使用 flex-wrap: wrap,我们不再有一条可以串起每个项目的主轴线。实际上,每一行都充当了自己的迷你弹性容器。每行都有自己的串,而不是 1 个大串:

到目前为止,我们学到的所有规则在这个缩小的范围内继续适用。例如,justify-content 会将两块分布在每根棍子上。

但是嗯...既然我们有多行,align-items是如何工作的?交叉轴现在可以与多个项目相交!

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

每一行都是它自己的迷你 Flexbox 环境。 align-items 将在环绕每一行的不可见框中向上或向下移动每个项目。

但是如果我们想自己对齐行呢?我们可以使用 align-content 属性来做到这一点:

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

  • flex-wrap: wrap 给了我们两行东西。
  • 在每一行中,align-items 允许我们将每个子项目向上或向下滑动
  • 但是,缩小后,我们在单个 Flex 上下文中有这两行!交叉轴现在将与两行相交,而不是一行。因此,我们不能单独移动行,我们需要将它们作为一个组进行分配。
  • 使用我们上面的定义,我们处理的是内容,而不是项目。但我们还在谈论交叉轴!所以我们想要的属性是 align-content。

版权声明:著作权归作者所有。

thumb_up 0 | star_outline 0 | textsms 0