弹性项目不是网格列 场景:一行中三个不同大小的文本项目 老式酷炫的弹性框网格 差异 结论 尾注:表格

2025-06-07

弹性项目不是网格列

场景:一行中三个不同大小的文本项

老派酷

弹性盒子

网格

区别

结论

尾注:表格

我们经常谈论 Flexbox 的增大-收缩规则,例如flex-growjustify/align-content/items,它们的工作方式与frCSS Grid 引入的单位基本相同。对于熟悉 Flexbox 的开发者来说,这可以作为 s 的入门介绍fr,但事实并非如此。有时会出现一个微妙但非常重要的区别。

场景:一行中三个不同大小的文本项

让我先来描述一个场景,就像我喜欢做的那样。这是我经常遇到的一个常见场景的一个特例,而这个常见的问题被 CSS Grid 巧妙地解决了。

假设我们有 3 个不同大小的内联元素(例如文本标签、按钮或图像),需要将它们整齐地显示在一行中。我们希望第一个标签左对齐,最后一个标签右对齐,中间的标签居中对齐。

下面是一个我大学时期周末项目的例子(我希望在以后的文章中更详细地介绍它,但这与主题无关)。这个项目是对 HTML5<canvas>元素的一次实验,其中包含键盘和鼠标交互的元素。它包含三个主要控件,我在画布上方用三个文本标签列出了这些控件:

三个文本标签

这就是目标。最重要的要求,也是最容易出问题的要求,是中间项必须位于页面中央:它的中心线应该与页面的中心线对齐。(在某些情况下,“页面”就是指“容器”,但“页面”更容易理解。)我在标签上方添加了标题,以便提供一个有用的视觉参照点来进行比较。

老派酷

回到大学,在 2012 年左右 flexbox 出现之前,我用一些非常脆弱的 HTML+CSS 解决了这个问题,这些 HTML+CSS 使用了很多绝对定位:

我已将第一个和最后一个标签与中间元素定位在同一行,并固定在左侧或右侧。这……当然有效,但可能会被周围布局的某些变化所干扰,反应非常迟钝,可读性很差,而且不灵活:如果你想添加第四个控件,一切都会变得复杂得多。

弹性盒子

如果我们要用现代 CSS 来实现这个功能,第一个想到的可能是 Flexbox。这当然是我的第一个想法;这是一个一维布局的情况,而 Flexbox 最擅长。而且,我非常确定我可以用两行 CSS 来实现:

#controls-body {
    display: flex;
    justify-content: space-between;
}
Enter fullscreen mode Exit fullscreen mode

完成了!对吧?

... 没有。

虽然我们确实有三个项目,一个拉到左边,一个拉到右边,还有一个浮动在它们之间,但你应该马上就能看到中间的项目没有正确居中;也就是说,它不是在页面的中央,而是在其他项目之间。如果两端的项目大小始终相同,那么这种方法可以正常工作,否则就不行。

您还可以尝试另一种方法,即不在父级上使用justify-contents,而是给所有列添加flex-grow: 1,然后为每个列添加一些text-align规则。但这实际上会产生完全相同的视觉效果:

为什么这些方法不起作用?我们马上就讲到。首先,我们先来看看哪些方法有效。

网格

让我们从第二次 Flexbox 尝试中汲取灵感,为每个内联项目赋予其自己的“列”,并使这些“列”具有相同的大小,然后将其转换到 CSS Grid,我们可以在其中删除引号:我们将在网格中为每个项目定义一列,并使用frGrid 引入的单位赋予它们相同的大小。

有两种方法可以做到这一点。最明显的是定义三个显式列:

#controls-body {
    grid-template-columns: 1fr 1fr 1fr;
}
Enter fullscreen mode Exit fullscreen mode

简洁又漂亮。但我希望我的代码能够面向未来,只要它不增加太多工作量或复杂性就行。因此,与其显式定义一组列,不如使用 Grid 的自动流规则来处理任意数量的项目,以便以后我想在演示中添加更多控件:

#controls-body {
    grid-auto-flow: columns;  /* automatically place new items in new columns */
    grid-auto-columns: 1fr;   /* auto-columns should be 1fr wide */
}
Enter fullscreen mode Exit fullscreen mode

将这些规则与text-align上述规则结合起来,我们就能得出一个赢家:

所以,三列的网格1fr可以正常工作,但包含三个元素的弹性容器flex-grow: 1(或者justify-content: space-between)却不行。为什么?它们之间有什么区别?

区别

事情是这样的。我们经常说 Flexbox 的弹性“均匀分布空间”规则,比如flex-growjustify-content: space-between,实际上与frCSS Grid 引入的单位相同。对于熟悉 Flexbox 但不熟悉 Grid 的开发者来说,这可以直观地解释frs 的含义,但事实并非如此。有时会出现一个微妙但非常重要的区别,这将解释这里的差异。

在 Flexbox 中,space-between、等尺寸会在所有项目放入区域后flex-grow划分剩余空间100px。因此,如果你有一个大小为 的容器justify-content: space-between(就像我们这里一样),以及三个大小分别为10px40px和 的项目20px,那么 Flexbox 会进行以下计算(大致):

remainder = available space - total used space
          = 100px - (10px + 20px + 40px) = 100px - 70px
          = 30px
space between items = remainder / (# children - 1)
                    = 30px / (3-1) = 30px / 2
                    = 15px
Enter fullscreen mode Exit fullscreen mode

最终的布局是:

10px item | 15px space | 40px item | 15px space | 20px item
Enter fullscreen mode Exit fullscreen mode

问题在于居中效果没有保持。那个 40px 项目的中心点位于10px + 15px + (40px/2 = 20px),也就是45px。所以它距离容器的真实中心(即 )有 5 个像素的偏差50px

网格的fr单位有所不同。它会先布局分配给网格项的总空间,然后再考虑该项的大小。因此,如果我们使用 来将100px容器设置为网格容器grid-template-columns: 1fr 1fr 1fr,那么网格会在考虑网格项的内容之前进行以下计算:

remainder = available space - (fixed size column widths)
          = 100px - 0
          = 100px
pixels per fr = remainder / (how many frs used across all column widths)
              = 100px / (1fr + 1fr + 1fr) = 100px / 3
              = 33.333px

So each `1fr` column gets 33.333px, an even third of the space. This means the center of the second column will fall at `33.333px + (half of 33.333px = 16.667px)`, which is a nice round `50px`!

> One very important thing that I totally didn't explain up there is the `fixed size column widths` variable, which in our case is `0`. This is the sum of any columns with values defined in a fixed unit, like `px`, `rem`, `in` (yeah, you can use inches in CSS! Nice for print layouts), or even `%`, which is fixed for the current size of the browser window. So for example if we used the rule `grid-template-columns: 30px 1fr 1fr`, then the calculation changes to this:
>```

python
remainder = available space - (fixed size column widths)
          = 100px - 30px
          = 70px
pixels per fr = remainder / (how many frs used across all column widths)
              = 70px / (1fr + 1fr) = 70px / 2
              = 35px


Enter fullscreen mode Exit fullscreen mode

因此在这种情况下,每1fr列为 35px。

结论

希望本文能帮助大家更深入地了解 Flexbox 的工作原理,并给那些还在犹豫不决的人一个重新考虑 Grid 的理由。令我震惊的是,我遇到的仍然对 Grid 持强烈怀疑态度的人竟然有这么多。

最后,我想在这里澄清一点:我并不是说 Grid 的fr单元比 Flexbox 的行为更优越。绝对有一些情况,Flexbox 的行为正是你想要的。在很多情况下,在兄弟元素之间平均分配剩余空间比让它们都占据相同的空间更重要。Flexbox 还允许元素基于容器伸缩(“flex”)的方式更加复杂。也许我会找个时间写一篇关于这方面的文章。总之,我这里的意思绝不是说 Grid 的fr单元比 Flexbox 更好,只是说它们在这种特定情况下更容易使用。我仍然喜欢 Flexbox 💕💕💕


尾注:表格

唉。是是是,我知道,用表格就能做到。其实做得挺干净的,尤其是用 CSS 表格代替实际的表格的时候:


css
#controls-body {
  width: 100%;
  display: table;
  table-layout: fixed;
}
p {
  display: table-cell;
}


Enter fullscreen mode Exit fullscreen mode

再加上text-align规则,我们又获得了一位赢家:

老实说,对于我来说,这在 2012 年会是一个更好的解决方案,即使由于浏览器对 CSS 表格的支持有限而我不得不使用实际的表格。

但我对此最大的疑问在于。这不仅仅是那句经常被重复却很少被解释的至理名言:“永远不要用表格布局”(不过,说真的,既然有了网格,就永远不要用表格布局)。在网格出现之前的时代,这确实是一个不错的解决方案;它简洁明了,足够健壮,可以处理添加额外项目,而且代码可读性强,只要你知道什么是 CSS 表格。

不,我的问题就一个词:响应性。因为它缺乏响应性,尤其是用 HTML 表格时,CSS 表格也好不到哪里去。当然,你可以把所有项目放到一列,以便在移动端查看。但假设你有 4 列,并且想要一个 2x2 的中等大小布局。用表格该怎么做?如果不重新回到绝对定位的诡计,你很难做到这一点。

我不会说这不可能;我已经想到了几个方案,但它们并不完美,而且都有缺点。最重要的是,大多数(如果不是全部)方案都特定于当前的项目数量,这使得代码很脆弱。

另一方面,网格的响应速度惊人。重新定义网格的行和列,或者以不同的方式在网格上放置元素都非常简单,因此至少设置一个媒体查询断点会很容易。(我写了一整篇文章来赞美网格区域,这个功能让断点变得更加实用:CSS 网格区域很棒。)但在很多情况下,断点是不必要的;网格从一开始就考虑到了响应速度,并且拥有许多支持响应速度的功能,例如repeat(auto-fill, ...)minmax()

有关这方面的更多信息,我强烈建议您查看 Rachel Andrew 的gridbyexample.com和 Jen Simmons 的Layout Land

文章来源:https://dev.to/kenbellows/flex-items-are-not-grid-columns-4p2i
PREV
String.prototype.search():我希望很久以前就知道的方法
NEXT
如何在 ExpressJS 中处理密码重置