交互式教程教你精通掌握 CSS Grid 布局

简介

CSS Grid 布局是 CSS 语言中最神奇的部分之一。它为我们提供了大量新工具,让我们可以利用这些工具创建精致流畅的布局。

它的复杂程度也令人吃惊。我花了很长时间才真正熟悉 CSS Grid 布局!

在本教程中,我将与大家分享我在使用 CSS Grid 布局的过程中遇到的最大的 💡 亮点。你将学习到这种布局模式的基本原理,并了解如何使用它做一些很酷的事情。✨

CSS 由几种不同的布局算法组成,每种算法都针对不同类型的用户界面而设计。默认的布局算法--流式布局,是专为数字文档设计的。表格布局专为表格数据而设计。Flexbox 专为沿单个轴分布项目而设计。

CSS Grid 是最新、最棒的布局算法。它的功能非常强大:我们可以用它来构建复杂的布局,并根据一系列约束条件进行流畅调整。

在我看来,CSS Grid 最与众不同的地方在于,它的网格结构、行和列完全由 CSS 定义:

通过 CSS 网格,单个 DOM 节点被细分为行和列。在本教程中,我们用虚线突出显示行/列,但实际上它们是不可见的。

这太奇怪了!在其他所有布局模式中,创建类似隔间的唯一方法就是添加更多 DOM 节点。例如,在表格布局中,每一行都使用 <tr> 创建,该行中的每个单元格都使用 <td><th>

								
html

与表格布局不同,CSS 网格让我们可以完全在 CSS 中管理布局。我们可以随心所欲地分割容器,创建网格子元素可用作空间的隔间。

我们通过display属性选择进入网格布局模式:

								
css

默认情况下,CSS 网格使用单列,并根据子元素的数量按需创建行。这被称为隐式网格,因为我们没有明确定义任何结构。

以下是其工作原理:

隐式网格是动态的;将根据子网格的数量添加和删除行。每个子网格都有自己的行。

默认情况下,父网格的高度由其子网格决定。它可以动态增长和收缩。有趣的是,这甚至不是 "CSS Grid";网格 parent 仍使用 Flow 布局,Flow 布局中的块元素会垂直生长,以包含其内容。只有子元素才使用网格布局。

但如果我们给网格一个固定的高度呢?在这种情况下,总面积会被分成大小相等的行:

默认情况下,CSS Grid 将创建单列布局。我们可以使用 grid-template-columns 属性指定列:

Code Playground

Result

Enable ‘tab’ key

通过向 grid-template-columns 传递 25%75% 这两个值,我告诉 CSS 网格算法将元素分成两列。

可以使用任何有效的 CSS < length-percentage > 来定义列,包括像素、rems、视口单位等。此外,我们还可以使用一个新单位,即 fr 单位:

Code Playground

Result

Enable ‘tab’ key

fr 代表 "分数"。在这个例子中,我们说第一列应该占用 1 个单位的空间,而第二列占用 3 个单位的空间。也就是说,总共有 4 个单位的空间,这就是分母。第一列占用可用空间的 1/4,而第二列占用 3/4。

fr 单位为 CSS 网格带来了 Flexbox 风格的灵活性。百分比和 <length> 值会产生硬约束,而 fr 列则可以根据需要自由增减,以容纳其内容。

试试缩小这个容器,看看有什么不同:

在这种情况下,我们的第一列有一个可爱的幽灵,它的宽度明确为 55px。但如果该列太小,无法容纳它,该怎么办呢?

  • 基于百分比的列是刚性的,因此我们的幽灵图片会溢出,从列中溢出。
  • 基于 fr 的列是灵活的,因此列不会缩小到最小内容尺寸以下,即使这意味着要打破比例。

更准确地说:fr 单位分配额外空间。首先,将根据内容计算列宽。如果有剩余空间,将根据 fr 值进行分配。这与《Flexbox 交互式指南》中讨论的 flex-grow 非常相似。

总的来说,这种灵活性是件好事。百分比过于严格。

gap 是一个神奇的 CSS 属性,它可以在网格中的所有列和行之间添加固定的空间。

看看我们在百分比和分数之间切换时会发生什么:

注意到使用基于百分比的列时,内容是如何溢出网格父级的?出现这种情况是因为百分比是使用网格总面积计算的。这两列占用了父网格 100%的内容区域,而且不允许缩小。当我们添加 16px 的间隙(gap)时,这两列内容就只能溢出容器之外了。

相比之下,fr 单位是根据额外空间计算的。在本例中,额外空间减少了 16px ,即间隙(gap)。CSS 网格算法会在两列网格之间分配剩余空间。

Link to this heading
隐式和显式行

如果在双列网格中添加两个以上的子网格,会发生什么情况?

好吧,让我们试一试:

Code Playground

Result

Enable ‘tab’ key

有意思我们的网格增加了第二行。网格算法希望确保每个子网格都有自己的网格单元。为了实现这一目标,它会根据需要生成新行。如果我们的item数量不固定(例如照片网格),而我们又希望网格能够自动扩展,那么这就非常方便了。

但在其他情况下,我们需要明确定义行以创建特定布局。我们可以使用 grid-template-rows 属性来做到这一点:

Code Playground

Result

Enable ‘tab’ key

通过定义 grid-template-rowsgrid-template-columns,我们创建了一个显式网格。这非常适合构建页面布局,比如本教程顶部的 “Holy Grail” ? 布局

假设我们正在制作一个日历:

CSS 网格是实现此类功能的绝佳工具。我们可以将其结构化为 7 列网格,每列占用 1 个单位的空间:

								
css

这样做是可行的,但要计算每一个 1fr 有点烦人。试想一下,如果我们有 50 个列!

幸运的是,有一种更好的方法可以解决这个问题:

								
css

repeat 功能将为我们完成复制/粘贴。我们需要 7 列,每列宽 1fr

如果你想知道完整的代码,这里是显示完整代码的示例

Code Playground

Result

Link to this heading
分配子单元

默认情况下,CSS 网格算法会将每个子网格分配到第一个未占用的网格单元,就像工人在浴室地板上铺瓷砖一样。

不过最酷的一点是:我们可以将 item 分配到任何我们想要的单元格中!子单元甚至可以跨越多行/列。

下面是一个交互式演示,展示了如何操作。点击/按下并拖动,在网格中放置子单元格*:

通过 grid-rowgrid-column 属性,我们可以指定网格子元素应占用的轨道。

如果我们想让网格子元素占据某一行或某一列,可以用其编号来指定grid-column: 3 将设置子网格位于第三列。

网格子网格也可以跨越多行/列。其语法使用斜线来划分起点和终点:

								
css

乍一看,这就像一个分数,即 ¼。但在 CSS 中,斜线字符不是用来分割的,而是用来分隔一组数值。在本例中,它允许我们在单个声明中设置起始列和终止列。

这基本上是一种速记:

								
css

这里有一个隐秘的陷阱: 我们提供的数字基于列行,而不是列索引。

通过图表最容易理解这个问题:

Confusingly, a 4-column grid actually has 5 column lines. When we assign a child to our grid, we anchor them using these lines. If we want our child to span the first 3 columns, it needs to start on the 1st line and end on the 4th line.

Alright, time to talk about one of the coolest parts of CSS Grid. 😄

Let's suppose we're building this layout:

Using what we've learned so far, we could structure it like this:

								
css

This works, but there's a more ergonomic way to do this: grid areas.

Here's what it looks like:

Like before, we're defining the grid structure with grid-template-columns and grid-template-rows. But then, we have this curious declaration:

								
css

Here's how this works: We're drawing out the grid we want to create, almost as if we were making ASCII art ? . Each line represents a row, and each word is a name we're giving to a particular slice of the grid. See how it sorta looks like the grid, visually?

Then, instead of assigning a child with grid-column and grid-row, we assign it with grid-area!

When we want a particular area to span multiple rows or columns, we can repeat the name of that area in our template. In this example, the “sidebar” area spans both rows, and so we write sidebar for both cells in the first column.

Should we use areas, or rows/columns? When building explicit layouts like this, I really like using areas. It allows me to give semantic meaning to my grid assignments, instead of using inscrutable row/column numbers. That said, areas work best when the grid has a fixed number of rows and columns. grid-column and grid-row can be useful for implicit grids.

Link to this heading
Being mindful of keyboard users

There's a big gotcha when it comes to grid assignments: tab order will still be based on DOM position, not grid position.

It'll be easier to explain with an example. In this playground, I've set up a group of buttons, and arranged them with CSS Grid:

Code Playground

Result

In the “RESULT” pane, the buttons appear to be in order. By reading from left to right, and from top to bottom, we go from one to six.

If you're using a device with a keyboard, try to tab through these buttons. You can do this by clicking the first button in the top left (“One”), and then pressing Tab to move through the buttons one at a time.

You should see something like this:

The focus outline jumps around the page without rhyme or reason, from the user's perspective. This happens because the buttons are being focused based on the order they appear in the DOM.

To fix this, we should re-order the grid children in the DOM so that they match the visual order, so that I can tab through from left to right, and from top to bottom.

In all the examples we've seen so far, our columns and rows stretch to fill the entire grid container. This doesn't need to be the case, however!

For example, let's suppose we define two columns that are each 90px wide. As long as the grid parent is larger than 180px, there will be some dead space at the end:

We can control the distribution of the columns using the justify-content property:

If you're familiar with the Flexbox layout algorithm, this probably feels pretty familiar. CSS Grid builds on the alignment properties first introduced with Flexbox, taking them even further.

The big difference is that we're aligning the columns, not the items themselves. Essentially, justify-content lets us arrange the compartments of our grid, distributing them across the grid however we wish.

If we want to align the items themselves within their columns, we can use the justify-items property:

When we plop a DOM node into a grid parent, the default behaviour is for it to stretch across that entire column, just like how a <div> in Flow layout will stretch horizontally to fill its container. With justify-items, however, we can tweak that behaviour.

This is useful because it allows us to break free from the rigid symmetry of columns. When we set justify-items to something other than stretch, the children will shrink down to their default width, as determined by their contents. As a result, items in the same column can be different widths.

We can even control the alignment of a specific grid child using the justify-self property:

Unlike justify-items, which is set on the grid parent and controls the alignment of all grid children, justify-self is set on the child. We can think of justify-items as a way to set a default value for justify-self on all grid children.

So far, we've been talking about how to align stuff in the horizontal direction. CSS Grid provides an additional set of properties to align stuff in the vertical direction:

align-content is like justify-content, but it affects rows instead of columns. Similarly, align-items is like justify-items, but it handles the vertical alignment of items inside their grid area, rather than horizontal.

To break things down even further:

  • justify — deals with columns.
  • align — deals with rows.
  • content — deals with the grid structure.
  • items — deals with the DOM nodes within the grid structure.

Finally, in addition to justify-self, we also have align-self. This property controls the vertical position of a single grid item within its cell.

Link to this heading
Two-line centering trick

There's one last thing I want to show you. It's one of my favourite little tricks with CSS Grid.

Using only two CSS properties, we can center a child within a container, both horizontally and vertically:

The place-content property is a shorthand. It's syntactic sugar for this:

								
css

As we've learned, justify-content controls the position of columns. align-content controls the position of rows. In this situation, we have an implicit grid with a single child, and so we wind up with a 1×1 grid. place-content: center pushes both the row and column to the center.

There are lots of ways to center a div in modern CSS, but this is the only way I know of that only requires two CSS declarations!

Link to this heading
Tip of the iceberg

In this tutorial, we've covered some of the most fundamental parts of the CSS Grid layout algorithm, but honestly, there's so much more stuff we haven't talked about!

If you found this blog post helpful, you might be interested to know that I've created a comprehensive learning resource that goes way deeper. It's called CSS for JavaScript Developers.

The course uses the same technologies as my blog, and so it's chock full of interactive explanations. But there are also bite-sized videos, practice exercises, real-world-inspired projects, and even a few mini-games.

If you found this blog post helpful, you'll love the course. It follows a similar approach, but for the entire CSS language, and with hands-on practice to make sure you're actually developing new skills.

It's specifically built for folks who use a JS framework like React/Angular/Vue. 80% of the course focuses on CSS fundamentals, but we also see how to integrate those fundamentals into a modern JS application, how to structure our CSS, stuff like that.

If you struggle with CSS, I hope you'll check it out. Gaining confidence with CSS is game-changing, especially if you're already comfortable with HTML and JS. When you complete the holy trinity, it becomes so much easier to stay in flow, to truly enjoy developing web applications.

You can learn more here:

I hope you found this tutorial useful. ❤️

Last Updated

November 22nd, 2023

Hits