Flexbox交互式指南

本文翻译自《An Interactive Guide to Flexbox》。

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

例如,看看这个:

(译者注:案例请看原文)

此案例很大程度上受到Adam Argyle的出色的“4种布局,1种价格”的启发。它不使用媒体/容器查询。它没有设置任意断点,而是使用流畅的原则来创建无缝流动的布局。 以下是此案例的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;
}

我记得我曾经遇到过这样的演示,当时完全不知所措。我知道 Flexbox 的基础知识,但这似乎是绝对的魔法!

在这篇博文中,我想完善您对 Flexbox 的心理模型。我们将通过了解每个属性来建立对 Flexbox 算法如何工作的直觉。无论您是 CSS 初学者,还是已经使用 Flexbox 多年,我敢打赌您都会学到很多东西!

让我们开始吧!

内容警告

我在本教程后面会做一个与食物相关的比喻。

Flexbox简介

CSS由许多不同的布局算法组成,布局算法的正式名称是“布局模式(layout mode)”。每种布局模式都是CSS中的一种小语言。默认布局模式是流式布局(Flow layout),但我们可以通过更改父容器上的display属性来选择使用Flexbox布局模式:

(译者注:案例请看原文)

当我们将display属性的值设置为flex时,我们会创建一个“flex格式化上下文”。这意味着,所有子元素都将根据Flexbox布局算法进行定位。

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

那么,Flexbox解决了什么问题?Flexbox就是将一组条目(元素)排列成一行或一列,并赋予我们对这些项目的分布和对齐方式的极大控制权。顾名思义,Flexbox就是“灵活性”。我们可以控制条目是增大还是缩小,如何分配额外的空间等等。

它仍然有意义吗?

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

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

当涉及到以垂直或水平列表排列项目的动态、流畅的UI时,Flexbox仍然占据主导地位。我们将在本指南中看到一个示例,即解构的煎饼,这无法通过CSS Grid轻松实现。

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

Flex方向

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

(译者注:案例请看原文)

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

在Flexbox中,一切都基于主轴。算法不关心垂直或水平,甚至行或列。所有规则都围绕此主轴和垂直延伸的横轴构建。

这非常酷。当我们学习Flexbox的规则时,我们可以无缝地从水平布局切换到垂直布局。所有规则都会自动适应。此功能是Flexbox布局模式所独有的。

默认情况下,子元素将根据以下2条规则进行定位:

  • 主轴(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是一种语法糖,是一种方便的简写,可以自动同时设置所有子元素的在副轴上的对齐方式。

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

内容 vs 条目(子元素)

因此,根据我们目前所学的知识,Flexbox可能看起来相当随意。为什么名字是justify-content和align-items,而不是justify-items或align-content?

还有上文那个问题,为什么有align-items属性,而没有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属性时介绍它。

假尺寸(hypothetical size)

让我们来谈谈我对Flexbox最令人大开眼界的认识之一。假设我有以下 CSS:

.item {
  width: 2000px;
}

一个理性的人看到这个可能会说:“好吧,所以我们会得到一个宽度为2000像素的项目”。但这总是正确的吗?让我们测试一下:

(译者注:案例请看原文)

这很有趣,不是吗?两个条目应用了完全相同的CSS。它们各自的宽度都是:2000px。然而,第一个条目比第二个条目宽得多!

不同之处在于布局模式。第一个条目使用流式布局进行渲染,在流式布局中,宽度是一个硬性约束。当我们设置宽度:2000px时,我们将得到一个2000像素宽的元素,即使它必须冲破视口的一侧。

然而,在Flexbox中,width属性的实现方式不同。它更像是一种建议,而不是硬性约束。规范对此有一个名称:假设尺寸。它是元素在完美的乌托邦世界中的尺寸,当没有任何阻碍时。

唉,事情很少这么简单。在这种情况下,限制因素是父元素没有空间容纳2000px宽的子元素。因此,子元素的尺寸会缩小以适应它。

这是Flexbox理念的核心部分。事物是流动的、弹性的,可以适应世界的限制。

算法的输入

我们倾向于将CSS语言视为属性的集合,但我认为这是错误的思维模型(mental model)。正如我们所见,width属性的行为会根据所使用的布局模式而有所不同!

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

换句话说,我们编写的CSS是这些算法的输入,就像传递给函数的参数一样。如果我们想真正熟悉CSS,仅仅学习CSS属性是不够的;我们必须了解算法如何使用这些属性。(译者注:这就是很多CSS新手说“CSS很难学”、“CSS属性的功能不正交”的原因了,因为学习和使用CSS的思维模式不正确)

增长与收缩

因此,我们已经看到Flexbox算法具有一些内建的弹性,具有假尺寸。但要真正了解Flexbox的弹性,我们需要讨论3个属性:flex-grow、flex-shrink和flex-basis。 让我们看看这些属性。

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,但你可以调整第一个子元素:

(译者注:案例请看原文)

就像我们在width方面看到的一样,flex-basis更多的是一种建议尺寸,而不是硬性约束。在某个时候,空间不足以让所有元素都按照指定的大小放置,因此它们必须做出妥协,以避免溢出。

不完全一样

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

这远远超出了这篇博文的范围,但我想提一下,因为你可能偶尔会遇到两个属性具有不同效果的边界情况。

flex-grow

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

我们可以使用flex-grow属性来指定如何使用该空间:

(译者注:案例请看原文)

flex-grow的默认值是0,这意味着flex-grow是可选的。如果我们想让某个子元素占用容器中的所有额外空间,我们需要明确地告诉它。

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

我认为用可视化方式解释会更容易。尝试增加/减少每个子元素的flex-grow的值:

(译者注:案例请看原文)

例如,第一个子元素想要4个单位的额外空间,而第二个子元素想要1个单位。这意味着单位总数是5 (=4 + 1)。每个子元素都会按比例获得该额外空间的份额(第一个子元素获得4/5的额外空间,第二个子元素获得1/5的额外空间)。

flex-shrink

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

让我们测试一下。尝试缩小容器的宽度以查看会发生什么:

(译者注:案例请看原文)

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

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

flex-basis和width设定元素的假设尺寸。Flexbox算法可能会将元素缩小到低于它所需尺寸,但默认情况下,它们将始终一起缩放,从而保持两个元素之间的比例。

现在,如果我们不想让元素按比例缩小怎么办?这就是flex-shrink属性的作用所在。

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

(译者注:案例请看原文)

我们有两个子元素,每个元素的假设尺寸为250px。容器的宽度至少需要500px,才能容纳假设尺寸的这些子元素。

假设我们将容器缩小到400px。好吧,我们无法将价值500px的内容塞进400px的袋子里!我们缺口是100px。我们的元素将需要放弃总共100px,才能适合。

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

和flex-grow一样,它是一个比例。默认情况下,两个子元素都有flex-shrink: 1,因此每个子元素都支付缺口的1/2。它们各自放弃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子元素具有相同的假设尺寸(250px)。在确定如何收缩它们时,我们可以使用flex-shrink进行专门计算。

不过,正如我们之前所看到的,收缩算法也会尝试保持兄弟元素之间的比例。如果第一个子元素的大小是第二个的2倍,那么第一个子元素的收缩幅度会更大。

因此,完整的计算涉及每个子元素的flex-shrink值和相对大小。

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

  • flex-grow控制当子元素小于其容器时如何分配额外空间
  • flex-shrink控制当子元素大于其容器时如何移除空间

这意味着同一时刻,这两个属性中只有一个可以处于激活的状态。如果有多余的空间,flex-shrink不会产生任何效果,因为子元素不需要缩小。如果子元素对于其容器来说太大,flex-grow不会产生任何效果,因为没有多余的空间可以分配。

我喜欢把它想象成两个不同的世界。你要么在地球上,要么在相反的世界。每个世界都有自己的规则。

防止收缩

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

我经常在SVG图标和形状中发现这种情况。让我们看一个简化的示例:

(译者注:案例请看原文)

当容器变窄时,我们的两个圆圈会被挤压成椭圆形。如果我们想让它们保持圆形怎么办?

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

(译者注:案例请看原文)

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

如果你感兴趣,以下是此演示的完整代码:

(译者注:代码请看原文)

更简单的方法?

有时,有人会想知道为什么我们有更简单的方法可以使用,却要费尽心思使用flex-shrink:

.item.ball {

  min-width: 32px;

}

几年前,我会同意这一点。如果我们设置最小宽度,子元素将无法缩小到该点以下!我们添加了一个硬约束,而不是width / flex-basis的软约束。

我认为这是很容易将“熟悉”与“简单”混淆的情形之一。你可能对min-width比flex-shrink更熟悉,但这并不意味着flex-shrink更复杂!

经过几年的实践,我实际上觉得设置flex-shrink: 0是解决这个特定问题的更直接的方法。不过,min-width在Flexbox算法中仍然发挥着重要作用!我们接下来会讨论这一点。

最小尺寸陷阱

这里还有一件事需要讨论,而且非常重要。这可能是整篇文章中最有帮助的事情!

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

(译者注:案例请看原文)

当容器收缩到某个点以下时,内容就会溢出

但为什么呢?flex-shrink的默认值是1,而我们并没有把它设置为0(阻止收缩),所以搜索输入框应该能够根据需要收缩!为什么它拒绝收缩呢?

情况是这样的:除了假设尺寸之外,Flexbox算法还关心另一个重要尺寸:最小尺寸(the minimum size)。

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

文本输入框的默认最小尺寸为170px-200px(不同浏览器会有所不同)。这就是我们上面遇到的限制。

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

(译者注:案例请看原文)

对于包含文本的元素,最小宽度是最长的不可断开的单词字符串的长度。 好消息是:我们可以使用min-width属性重新定义最小尺寸

(译者注:案例请看原文)

通过直接Flex子元素上设置min-width: 0px(译者注:在Flex容器的直接子元素上设置min-width: 0px,如果在孙元素上设置min-width: 0px是没有效果的),我们告诉Flexbox算法覆盖内置(默认)最小宽度。因为我们将其设置为0px,所以元素可以根据需要缩小。

相同的技巧可以在具有min-height属性的Flex列中发挥作用(尽管这个问题似乎并不经常出现)。

注意事项

值得注意的是,内置的最小尺寸确实有其用途。它起到护栏的作用,防止发生更糟糕的事情。

例如:当我们将min-width: 0px应用于包含文本的Flex子元素时,情况会变得更糟:

(译者注:案例请看原文)

能力越大,责任越大,对于Flexbox来说,min-width 一个特别强大的属性。它不止一次让我摆脱困境,但我总是小心翼翼,确保不会让事情变得更糟!

间隙

近年来提升Flexbox开发体验的最大的改进之一是gap属性:

(译者注:案例请看原文)

gap允许我们在每个Flex子元素之间创建间隙。这对于水平导航栏的标题子元素等场景非常有用:

(译者注:案例请看原文)

gap是Fl​​exbox布局中一个相对较新的功能,但自2021年初以来,它已在所有现代浏览器中实现

自动边距(Auto margins)

我还想分享一个与间距相关的技巧。这个技巧早在Flexbox早期就出现了,但相对来说不太为人所知,当我第一次发现它时,我感到非常震惊。

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

Flexbox中的自动边距更加有趣:

(译者注:案例请看原文)

之前,我们看到了flex-grow属性如何吞噬任何额外的空间,并将其应用于子元素。

自动边距将占用额外空间,并将其应用于元素的边距(margin)。它使我们能够精确控制在何处分配额外空间。

常见的顶部水平导航栏的布局是一侧显示logo,另一侧显示一些导航链接。以下是使用自动边距构建此布局的方法:

(译者注:案例请看原文)

Corpatech logo是列表中的第一个列表项。通过为其指定margin-right: auto,我们收集了所有额外空间,并将其强制置于第1个和第2个列表项之间。

我们可以使用浏览器devtools查看这里发生的情况:

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

换行(Wrapping)

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

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

(译者注:案例请看原文)

大多数时候,当我们在二维空间中工作时,我们会想要使用CSS Grid布局,但Flexbox + flex-wrap绝对有其用途!这个特殊的例子展示了“解构煎饼(deconstructed pancake)”布局,其中3个子元素在中等尺寸的屏幕上堆叠成倒金字塔形状。

当我们设置flex-wrap: wrap时,子元素不会缩小到其假设尺寸以下。至少,当可以选择换行到下一行或列时就不会缩小!

但是等一下!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方面都达到了早期的平稳期。我们有足够的知识来完成事情,但这是一场持续的斗争。CSS语言给人一种摇摇欲坠、难以捉摸的感觉,就像一座古老的绳桥,随时都可能断裂。当它断裂时,我们会向问题抛出随机的StackOverflow代码片段,希望能有所帮助。

这不好玩。这很糟糕,因为CSS是大多数前端开发工作中相当大的一部分! 事实上,CSS是一种非常健壮和一致的语言。问题是,我们的大多数心理模型都是不完整和不准确的。当我们花时间为CSS语言建立一个正确的直觉时,事情开始发生变化,它将成为一种绝对的乐趣。

Initial Data in Laravel Projects

Initial data for a project, such as roles (e.g., super admin role), permissions, and which users are granted admin privileges, is typically designed during the requirements analysis phase.

This initial data is a part of the project’s operation and will be used in the production environment. However, data seeding is generally used during the development phase.

Although Laravel doesn’t offer a built-in solution for this, we can leverage the database migration feature to achieve it. In terms of functionality, data migration is also part of the project, with the execution timing aligning perfectly with the installation of the project. The execution order is critical, ensuring that the initialization data is applied after the database table structure is created.

We can generate migration files for initializing data using the following command:

php artisan make:migration seed_categories_data

We define the naming convention for such migration files as seed_(table_name)_data.

For project initialization data, using a Seeder is less convenient than using a database migration file, especially in collaborative development. With multiple migration files from different developers, you only need to execute a single migration command. If issues arise, you can easily roll back, whereas Seeders lack rollback functionality. Rollback is crucial, both in development and production environments. If the initial data can be fully determined before the project is deployed (which is almost impossible), then using a Seeder is acceptable and recommended. However, after the project goes live, it’s likely that the initial data will require modifications (such as additions, deletions, or updates). In such cases, using a Seeder is inappropriate, and database migration files should be used instead. Seeders are primarily used for generating test data, and they should not be used for altering or deleting production data.

Laravel项目的初始化数据

项目的初始化数据,例如一个应用程序有哪些角色(例如超级管理员角色)、哪些权限、授予哪些用户管理员权限是在需求分析阶段就设计好的。

项目的初始化数据是项目运行的一部分,在生产环境下也会使用到,而数据填充(Seeder)一般在开发时使用。

虽然Laravel没有自带此类解决方案,不过我们可以借助数据迁移功能来实现。在功能定位上,数据迁移也是项目的一部分,执行的时机刚好是在项目安装时。并且区分执行先后顺序,这确保了初始化数据发生在数据表结构创建完成后。

我们可以使用命令生成数据迁移文件,作为初始化数据的迁移文件:

php artisan make:migration seed_categories_data

我们定义这种迁移文件的命名规范为seed_(数据库表名称)_data

对于项目的初始化数据,使用Seeder没有使用数据库迁移文件来得方便,特别是在多人协作开发时,对于别的开发者提供的多个数据库迁移文件,你只需执行一条迁移命令即可,如果有问题还能回滚,Seeder没有回滚功能。回滚功能无论是对于开发环境还是生产环境都是很重要的。 如果项目的初始化数据在项目上线(部署到生产环境)之前,就能全部确定下来(这几乎不可能),那么使用Seeder是可以的,也是应该的。但是在项目上线之后,很可能还要对初始化数据进行增删改操作,此时再使用Seeder是不合适的,应该使用数据库迁移文件。Seeder主要用来生成测试数据,很难、也不要用Seeder来删改数据库数据。

Web应用的类型以及它们各自的理想实现/交付方式

本文翻译自Jason Miller的博客文章Application Holotypes

分析现实世界中的应用程序的特征很困难。我们经常看到有人对应用程序做出概括,既有漫不经心的,也有经过数据统计的:“单页应用程序比多页应用程序慢”或“TTI 低的应用程序加载速度更快”。然而,这些概括对于我们关心的应用程序的性能和架构特征都不太准确。我认为主要决定因素之一是产品的功能和设计约束,根据应用程序的功能和约束对其进行分类可以为每个应用程序面临的问题提供更有针对性和更有效的解决方案。

构建一组类别,以便有效地将应用程序分组是一种挑战:很难预测所有可能的组,每个组设定的边界都是主观的,可能会随着时间的推移而改变。此外,像这样的抽象分组可能很难推理或可视化。例如,我们可以向“胖客户端、以页面为中心的富媒体应用程序,具有离线浏览和用户生成内容的功能”的开发人员推荐哪些性能优化技术?如果转而问的是我们可以向“类似Instagram的应用程序”的开发人员推荐什么,这种讨论就更具体化、更容易进行了。

为了建立这种讨论框架,我们可以构建一个典型应用程序列表。这些应用程序既可以代表当今的网络,也可以基于我们预见到的开发人员为响应未来趋势和平台计划而做出的改变。为了方便起见,代表Web的长尾历史和遗留内容的部分典型应用程序更加通用,而代表当前和即将推出的部分典型应用程序可以更狭窄地限定讨论范围,以便提供更具体的建议。

每个典型应用程序都附有粗略的类别名称、额外的真实案例以及定义其架构的特征和约束。我还根据其架构提供了理想的部署实施和交付技术。

社交网络应用

  • 典型:Facebook
  • 其他案例:LinkedIn、Reddit、Google+
  • 特点:多面性、子应用程序、无限滚动内容、用户生成内容、实时更新、通知
  • 限制:过深的会话深度、大规模、实时更新、嵌入式内容的资源争用、嵌套应用程序、SEO
  • 理想实现方式:具有外壳和登录页面预渲染的单页应用程序。
  • 理想交付方式:独立显示模式下的 PWA。或TWA。

社交媒体应用

  • 典型:Instagram
  • 其他案例:Youtube、Twitter
  • 特点:富媒体、无限滚动内容、用户生成内容、实时更新、通知、可嵌入性、嵌入式内容
  • 限制:扩展会话深度、实时更新、嵌入式内容的资源争用、不间断媒体播放、SEO
  • 理想实现方式:具有应用程序外壳预渲染和缓存的单页应用程序。
  • 理想交付方式:独立显示模式下的 PWA。

店面应用

  • 典型:亚马逊
  • 其他案例:百思买、新蛋、Shopify(基于商店)
  • 特点:搜索、支付、可发现性、过滤和排序 限制:浅到中等会话深度、小交互、高购物车/结帐流失率、SEO
  • 理想实现方式:具有 CSR/SPA 接管或 turbolinks 样式转换的服务器呈现站点。
  • 理想交付方式:默认显示模式下的 PWA。

内容网站

  • 典型:CNN
  • 其他案例:FT、BBC、BuzzFeed、Engadget、Salon、Smashing Magazine、The Onion
  • 特点:可发现性、富媒体、嵌入式内容 限制:浅会话深度(~1)、广告和多变量测试的资源争用、SEO
  • 理想实现方式:具有轮播图风格过渡效果的服务器端渲染网站。
  • 理想交付方式:默认显示模式下的 PWA。

PIM应用

  • 典型:Gmail
  • 其他案例:Google 日历、Outlook.com、Fastmail
  • 特点:胖客户端、无限列表、嵌入式内容、富文本编辑、清理、MDI、存储、离线和同步、通知
  • 限制:延长会话长度、敏感且大量不可缓存的数据、高安全风险、经常离线
  • 理想实现方式:具有应用程序外壳缓存的单页应用程序。
  • 理想交付方式:独立显示模式下的 PWA。

生产力工具

  • 典型:Google Docs
  • 其他案例:Office.com、Zoho、Dropbox、Box
  • 特点:胖客户端、富文本编辑、离线和同步、文件系统、剪贴板、存储、图像处理、嵌入式内容
  • 限制:延长会话长度和多个并发会话有利于客户端实现。
  • 理想实现方式:单页应用程序。考虑应用程序前端缓存。
  • 理想交付方式:独立显示模式下的 PWA。

媒体播放器

  • 典型:Spotify
  • 其他案例:Youtube Music、Google Play Music、Tidal、Soundcloud、Pandora、Deezer
  • 特点:富媒体、胖客户端、无限滚动内容、过滤和排序、通知、操作系统集成、离线、可嵌入性
  • 限制:延长会话长度,用户浏览其他页面时必须继续播放。
  • 理想实现方式:具有应用程序前端预渲染和缓存的单页应用程序。服务器渲染 <head> 以SEO友好。
  • 理想交付方式:独立显示模式下的 PWA。

图形编辑器

  • 典型:Figma
  • 其他案例:AutoCAD、Tinkercad、Photopea、Polarr
  • 特点:3D 渲染和 GPU、图像处理、全屏和指针捕获、MDI、存储、离线、文件系统、线程、wasm
  • 限制:会话长度长、对输入和渲染延迟的敏感性、大对象/文件
  • 理想实现方式:单页应用。将更轻量的浏览 UI 与编辑器分开。
  • 理想交付方式:独立显示模式下的 PWA。

媒体编辑器

  • 典型:Soundtrap
  • 其他案例:Looplabs
  • 特点:音频处理、设备集成(midi、usb)、存储、离线、文件系统、线程、wasm
  • 限制:长会话长度、低延迟 DSP、低延迟媒体录制和播放、大文件大小/IO
  • 理想实现方式:单页应用。将更轻量的浏览 UI 与编辑器分开。
  • 理想交付方式:独立显示模式下的 PWA。

工程工具

  • 典型:Codesandbox
  • 其他案例:Codepen、Jupyter Notebook、RStudio、StackBlitz
  • 特点:胖客户端、MDI、存储、离线、文件系统、线程、嵌入式内容
  • 限制:极长的会话长度、低延迟文本输入、大内存占用、自定义输入处理和文本渲染、预览内容的安全性
  • 理想实现方式:单页应用。考虑将浏览 UI 与编辑器分开。
  • 理想交付方式:独立显示模式下的 PWA。

沉浸式/AAA游戏

  • 典型:Stadia
  • 其他案例:Heraclos、Duelyst、OUIGO 特点:3D 渲染和 GPU、P2P、音频处理、全屏和指针捕获、存储、离线、文件系统、线程、设备集成(游戏手柄)、wasm
  • 限制:会话长度长(高度交互)、沉浸感、对输入和渲染延迟极其敏感、需要一致或分步的 FPS、极端的资产大小
  • 理想实现方式:单页应用
  • 理想交付:全屏显示模式下的 PWA。

休闲游戏

  • 典型:Robostorm
  • 其他案例:Tank Off、War Brokers、GoreScript、Air Wars、”.io games”
  • 特点:2D 和 3D 渲染和 GPU、P2P、音频处理、存储、离线、可嵌入性
  • 限制:会话长度长、对输入和渲染延迟敏感、需要一致/分步 FPS
  • 理想实现方式:单页应用
  • 理想交付方式:嵌入另一个站点,或全屏显示模式下的 PWA。

托管钱包和非托管钱包

钱包按是否被托管可以分为:

  • 非托管钱包(non-custodial wallets)(也称为自托管钱包),意味着用户始终可以控制自己的私钥和资金。非托管钱包有trust、MetaMask、Coinbase等。
  • 托管钱包(custodial wallets),意味着用户的私钥和资金都放在钱包软件公司的服务器里。托管钱包有Binance Wallet等。

托管钱包就像将你的贵重物品存放在仓库中,而非托管钱包就像将它们存放在家中的保险箱中。因此,托管钱包需要较少的个人责任,但受第三方支配,而非托管钱包由你自己完全控制,但也意味着你承担全部责任,确保你的物品安全并保管你的密码和密钥。

SSG就是页面静态化技术

静态站点生成 (Static-Site Generation,缩写为 SSG),也被称为预渲染,是另一种流行的构建快速网站的技术。如果用服务端渲染一个页面所需的数据对每个用户来说都是相同的,那么我们可以只渲染一次,提前在构建过程中完成,而不是每次请求进来都重新渲染页面。预渲染的页面生成后作为静态 HTML 文件被服务器托管。

SSG就是页面静态化技术,是门户网站、WordPress博客常用的Web优化手段之一。

SSR类似于PHP程序,每有一个HTTP请求到来就会被运行一次,渲染输出HTML字符串给前端。如果网站的内容没有实时性,我们可以把渲染输出的HTML字符串缓存一段时间,当有HTTP请求到来时,直接返回缓存给前端,而不是实时渲染输出。这就是页面静态化技术的核心思想。

参考

服务端渲染 (SSR)

Does Stopping a Docker Container Equate to Shutting Down a VirtualBox VM?

To clarify, stopping a running Docker container is not the same as pausing it. Stopping a container terminates the processes inside the container and releases associated resources (like CPU and memory), but the container’s filesystem and state remain intact, similar to how VirtualBox retains hard disk data after shutting down a VM. The container’s data stays unless explicitly deleted (using rm).

The Difference Between Stop and Pause:

  • Stop: Completely terminates all processes inside the container, similar to turning off the power. When the container is restarted, it begins from its configured entry process.
  • Pause: Freezes all processes inside the container, keeping their state intact, but without releasing resources. A paused container can be resumed, much like the Suspend feature in VirtualBox.

停止(注意不是暂停)运行docker容器是不是相当于VirtualBox的虚拟机关机?

具体来说,停止运行中的 Docker 容器会终止容器内部的进程,释放相关的资源(如 CPU 和内存),但容器的文件系统和状态会被保留,就像 VirtualBox 虚拟机关机后硬盘数据依然保存一样,除非进行删除(rm)操作。

停止和暂停的区别在于:

  • 停止(stop):完全终止容器内的进程,相当于电源关闭,容器在下次启动后会重新开始其配置的入口进程。
  • 暂停(pause):冻结容器内的所有进程,进程状态保持,但并不释放资源,暂停的容器可以恢复继续运行,类似于 VirtualBox 的暂停(Suspend)功能。

Methods for Website Internationalization (Providing Multi-language Content)

For websites that use server-side templating engines (e.g., Laravel’s Blade) to render HTML documents, the approach to localization (internationalization) is as follows: For common pages like login and registration, you can use the __() function to translate text based on the lang= query parameter passed from the frontend. For other pages, the language-specific views should be placed in directories like resources/views/{language_code} (for Laravel) or public/{language_code} (where “public” refers to the web root directory).

For fully decoupled applications, such as those where the frontend is developed using MVVM frameworks like Vue.js or React.js for SPAs, and the backend is built with Laravel to provide APIs, localization methods differ. In this case, you should add language-specific subdomains at the domain level, such as cn.vuejs.org for the Chinese Vue.js site, ja.vuejs.org for the Japanese site, with the default vuejs.org serving the English version. Additionally, the database should support language-specific partitioning, where each language code has its own set of tables or databases.