瞧,不用媒体查询!使用 CSS 网格实现响应式布局

Avatar of Juan Martín García
Juan Martín García

DigitalOcean 为您旅程的每个阶段提供云产品。立即开始使用 200 美元的免费额度!

CSS Grid 不仅重塑了我们思考和构建网页布局的方式,而且还有助于编写更具弹性的代码,取代了我们之前使用的“hacky”技术,在某些情况下,甚至无需依赖针对特定分辨率和视口的代码。网页开发这个时代的酷点在于,**我们能够用更少的代码做更多的事情。**

在本文中,我们将开始深入了解 CSS Grid 的强大功能,方法是构建几个常见的响应式导航布局。这比你想象的要容易,而且由于 CSS Grid 旨在实现响应式,因此它需要的代码量少于在各处编写媒体查询。让我们开始吧!

布局 #1:英雄内容和文章列表

查看 CodePen 上的示例
英雄内容和文章列表
,作者:Juan Martín García (@imjuangarcia)
CodePen 上。

我们将从创建常见的网站布局开始这一系列示例:一个全宽的英雄区域,下方是一个卡片网格。

这两个元素都将响应窗口大小调整并相应地进行调整。虽然乍一看这似乎需要很多代码,但响应式行为仅使用**六行 CSS Grid 代码完成,并且无需编写任何@media规则**。让我们分解代码以了解发生了什么

英雄区域

让我们看看.hero元素的代码

<section class="hero">
  <h1>You thirsty?</h1>
  <article>
    <p>Explore local breweries with just one click and stirred by starlight across the centuries light years great turbulent clouds circumnavigated paroxysm of global death.</p>
    <a href="#breweries">Browse Breweries</a>
  </article>
</section>
.hero {
  /* Photo by mnm.all on Unsplash */
  background: url('https://images.unsplash.com/photo-1518176258769-f227c798150e') center;
  background-size: cover;
  padding: 4rem 2rem;

  /* Grid styles */
  display: grid;
  align-items: center;
  grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
}

我们有一堆背景样式来启用啤酒背景,一些填充以将内容与屏幕边缘分隔开,然后是三行网格样式

  1. 第一行(display: grid;)将.hero元素的行为更改为网格容器。这意味着.hero内部的元素现在是网格项。
  2. 第二行(align-items: center;)将在我们的网格上垂直居中列。但是这两行本身不会有任何作用,直到我们设置网格的列。
  3. 这就是第三行发挥作用的地方。该单个属性中发生了很多事情,所以让我们一步一步来。

repeat() 函数

一般来说,我们通常在 CSS Grid 上定义列和行的方法是在定义属性后添加每个轨道的值,如下所示

.element {
  /* This will result on four columns, each one of 1fr */
  grid-template-columns: 1fr 1fr 1fr 1fr;
  /* This will result on two rows, each one of 300px */
  grid-template-rows: 300px 300px;
}

现在,这相当乏味。我们可以使用repeat()函数使其不那么冗长,更容易理解。该函数接受两个参数

  1. 重复值的次数。
  2. 值本身。

在将我们的代码重构为使用repeat()后,我们应该期望这些代码行产生相同的结果

.element {
  /* this is the same as grid-template-columns: 1fr 1fr 1fr 1fr; */
  grid-template-columns: repeat(4, 1fr);
  /* this is the same as grid-template-rows: 300px 300px; */
  grid-template-rows: repeat(2, 300px);
}

干净多了,对吧?

minmax() 函数

现在,上面的示例明确地为轨道定义了大小(1fr300px)。这可能适用于某些场景,但对于我们这里的啤酒示例,我们需要能够根据视口的宽度自动计算轨道的尺寸,并自动调整显示的列数。为了能够做到这一点,我们将使用minmax()函数定义一个值范围。我们将定义什么?您可能已经猜到了:我们希望这些列能够调整到的*最小*和*最大*值。

在我们上面啤酒示例的英雄区域中,我们将我们的minmax()属性设置为最小尺寸为 240px,最大尺寸为 1fr。如果您从未听说过fr单位,它们代表分数单位。Jen Simmons 在此视频中和 Robin Rendle 在此文章中对它们进行了更好的解释。

使用 Firefox 网格检查器检查调整大小时轨道尺寸的变化

这会导致我们的轨道在视口上有足够的空间时(桌面分辨率)为 1fr,而在没有足够空间容纳两列时(例如在移动设备上)为 240px。这就是为什么当我们使浏览器更宽时它们会很好地增长,因为它们正在获取剩余的空间并在现有列之间平均分配它。现在,让我们转到拼图的最后一块!

auto-fit 关键字

auto-fit关键字允许我们在视口没有足够的空间来适应 240px 最小值而不会溢出内容时,将我们的列换行到行中。Sara Soueidan 撰写了一篇优秀的文章,关于使用auto-fillauto-fit关键字自动调整列的大小,如果您想更深入地了解幕后发生的事情,可以阅读。现在,使用最后一段代码,我们应该能够实现以下结果

当视口中没有足够的空间时,列会自动换行

文章列表

现在我们已经彻底回顾了英雄元素内部元素的行为,您可能会发现下面啤酒厂列表的前两行 CSS 代码已经很熟悉了

.breweries > ul {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));
  grid-gap: 1rem;
}

没错!我们使用的是完全相同的方法:在第一行中我们定义了网格,在第二行中我们使用相同的魔法单行代码调整了轨道大小,在第三行中我们为这些列设置了间隙。并没有什么新鲜事,真正巧妙的是,我们的代码足够灵活,可以根据我们无序列表中项目的数量来调整轨道数量和大小

网格响应轨道数量的变化,并调整布局

就是这样,朋友们!一个完全响应式的网站布局,仅使用六行 CSS 代码。不错吧?请务必查看源代码并在 CodePen 上试用此示例。

布局 #2:全宽图片库

查看 CodePen 上的示例
全宽图片库
,作者:Juan Martín García (@imjuangarcia)
CodePen 上。

在这个下一个示例中,我们将充分利用我们新学到的repeat()auto-fitminmax()组合的强大功能来创建这个响应式图片库。我们还将使用grid-columngrid-row调整轨道大小,并了解方便的属性:grid-auto-flow: dense;值的组合,它允许我们更改无法适应我们显式轨道元素的默认行为:而不是将它们换行到新行或列中,我们将使它们适应网格上的未使用位置。让我们开始编码吧!

网格设置

网格是使用我们熟悉的 `display: grid;` 属性创建的,其中列的定义使用了 `repeat()`、`auto-fit` 和 `minmax()`。我们还使用 `repeat()` 函数添加了一堆行,并使用 `grid-gap` 为图像定义了间隙。但这里的新成员是 `grid-auto-flow: dense;`。我们稍后会详细介绍它。

.gallery > .gallery__list {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
  grid-template-rows: repeat(6, 200px);
  grid-gap: 1rem;
  grid-auto-flow: dense;
}

我们还使用 `nth-child()` 伪类选择器创建了一个重复模式,以使用 `grid-column` 和 `grid-row` 为我们的轨道设置不同的尺寸。请注意,我们在这里使用 `span` 关键字允许所选项目占用多列或多行。

/* This will create 2x images every 4 elements */
.gallery > .gallery__list > li:nth-child(4n) {
  grid-column: span 2; /* Spans two columns */
  grid-row: span 2; /* Spans two rows */
}

/* This will create 3x images every 8 elements */
.gallery > .gallery__list > li:nth-child(8n) {
  grid-column: span 3;
  grid-row: span 3;
}

最后,我们将确保我们的图像覆盖其容器的整个区域,无论它是 1x、2x 还是 3x,都使用 `object-fit: cover;`。如果您从未听说过 `object-fit`,它的工作原理与 `background-image` 非常相似,但使用 HTML 的 `` 标签。

.gallery > .gallery__list > li > figure > img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

现在,这里真正的重点是 `grid-auto-flow: dense;`。看看当我们从代码中删除它时会发生什么。

删除 `grid-auto-flow: dense;` 会导致元素在网格上的放置不一致。

看到我们精心制作的网格上的那些空洞了吗?这是因为其中一些元素占据了 2x 或 3x 的空间,并且当我们的轨道上没有足够的空间容纳它们时,它们会换行到新的一行,因为这是默认行为。通过将其从 `row` 更改为 `dense`,我们告诉网格使用可能适合它们的元素填充任何可能存在的间隙,无论它们在 DOM 中的源顺序如何。

这就是为什么这种技术可能特别适用于图像库等内容,但可能不适用于您可能需要保留标记顺序的其他用例。请随时使用 CodePen 演示 来查看项目放置位置之间的差异。

布局 #3:Trello 风格卡片布局

查看 Juan Martín García 编写的 Pen
Trello 风格卡片布局
(@imjuangarcia)
CodePen 上。

现在,进入最后一个演示,我们将利用嵌套网格的能力来重新创建这个 Trello 看板。我们将创建一个网格来容纳我们的四列,并在这些列内部,我们将为我们的卡片创建一个子网格。即使此示例不会探索新的属性或革命性的方法,它也将帮助我们了解使用几行 CSS 代码构建复杂布局是多么容易。此演示有很多额外的代码来实现 Trello 布局的样式,因此我们将只关注网格样式。

要创建四列,我们将在容器上使用 `display: grid;` 并使用我们神奇的一行代码来设置 `grid-template-columns`。我们还将在它们之间定义一个间隙,并使用 `align-items: flex-start;` 以确保我们的列不会拉伸到屏幕底部。

.column__list {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
  grid-gap: .5rem;
  align-items: flex-start;
}

现在,原始的 Trello 默认情况下不是响应式的:如果您调整 Trello 看板上浏览器的尺寸,您会注意到您最终会在列上出现水平滚动条,而不是将它们换行到新的一行。我们这里没有遵循这种行为,因为我们想要构建响应式布局,但是如果您好奇,并且想要模拟 Trello 的功能,您可以通过添加两行 CSS 代码来实现。

.column__list {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
  grid-gap: .5rem;
  align-items: flex-start;
  /* Uncomment these lines if you want to have the standard Trello behavior instead of the column wrapping */
  grid-auto-flow: column;
  grid-auto-columns: minmax(260px, 1fr);
}

我们在之前的演示中了解了 `grid-auto-flow`,并发现它允许我们控制自动放置算法的工作方式以及如何在网格流中添加隐式元素。默认行为是 `row`,这意味着任何无法容纳在网格中的额外元素都将换行到新的一行。我们在之前的演示中将其更改为 `dense`,并且在此演示中将其更改为 `column`:这样,此处添加的任何新列都将出现在隐式列中,并具有水平滚动条。我们还将使用 `grid-auto-columns` 属性为这些自动生成的列定义宽度。

修改 `grid-auto-flow` 属性将使此演示的行为与真实的 Trello 一致。

卡片

对于卡片网格,我们将使用类似的方法。我们将在容器上使用 `display: grid;`。我们不会在这里定义任何列,因为我们不希望有任何列,并且我们将使用 `grid-template-rows: auto;` 来避免所有卡片具有相同的高度——我们希望其中一些更大,一些更小,这取决于添加到它们中的内容类型。

.card__list {
  display: grid;
  grid-template-rows: auto;
  grid-gap: .5rem;
  margin: .5rem 0;
}

再次强调,就是这样!再添加两行代码来设置卡片的间隙和边距,我们就完成了!Pen 中的所有其他内容都是标准的 CSS,用于实现 Trello 的外观和感觉。

那么……媒体查询是否已死?

在过去,当我们使用 `display: inline-block` 或浮动构建布局时,媒体查询在更改元素尺寸(当视口变小时)方面很有意义。但是现在,凭借我们能够用几行 CSS 代码创建的强大布局,您可能会忍不住认为媒体查询注定要消亡。我强烈反对这种观点:我相信我们应该改变我们对它们的思考方式,因此应该以不同的方式使用它们。

正如 Rachel Andrew 大约一年前所说,我们应该在布局出现问题时使用媒体查询来修复它,而不是针对特定的设备:因为设备实在太多了!随着媒体查询 级别 45 的出现,我们不仅能够检测屏幕尺寸,还可以检测指针类型。因此,我们可以深入了解用户的系统偏好,并为那些偏好减少运动或是否应该使用反色的人调整我们的代码。这意味着媒体查询并没有消亡;相反,我认为现在是使用媒体查询的激动人心的时刻,但我们需要学习正确地使用它们。同时,使用 Flexbox 或 CSS Grid 等现代技术构建强大的布局将为您节省大量时间、代码和麻烦。