使用网格实现常见布局

为了结束这套关于 CSS 网格布局的指南,我们将逐步介绍一些不同的布局,这些布局演示了在使用网格布局进行设计时可以使用的一些不同技术。我们将查看一个使用grid-template-areas的示例、一个典型的 12 列弹性网格系统,以及一个使用自动放置的产品列表。正如您从这套示例中看到的那样,通常有多种方法可以实现您想要的网格布局结果。选择您认为最适合您要解决的问题和需要实现的设计的方法。

使用 grid-template-areas 实现响应式布局,包含 1 到 3 个流体列

许多网站都是这种布局的变体,包括内容、侧边栏、页眉和页脚。在响应式设计中,您可能希望将布局显示为单列,在某个断点添加侧边栏,然后为更宽的屏幕引入三列布局。

Image of the three different layouts created by redefining our grid at two breakpoints.

我们将使用我们在指南 网格模板区域 中了解到的 命名模板区域 创建此布局。

标记是一个包含元素的容器,用于页眉、页脚、主要内容、导航、侧边栏以及放置广告的块。

html
<div class="wrapper">
  <header class="main-head">The header</header>
  <nav class="main-nav">
    <ul>
      <li><a href="">Nav 1</a></li>
      <li><a href="">Nav 2</a></li>
      <li><a href="">Nav 3</a></li>
    </ul>
  </nav>
  <article class="content">
    <h1>Main article area</h1>
    <p>
      In this layout, we display the areas in source order for any screen less
      that 500 pixels wide. We go to a two column layout, and then to a three
      column layout by redefining the grid, and the placement of items on the
      grid.
    </p>
  </article>
  <aside class="side">Sidebar</aside>
  <div class="ad">Advertising</div>
  <footer class="main-footer">The footer</footer>
</div>

由于我们正在使用grid-template-areas 创建布局。在任何媒体查询之外,我们需要命名这些区域。我们使用grid-area 属性命名区域。

css
.main-head {
  grid-area: header;
}
.content {
  grid-area: content;
}
.main-nav {
  grid-area: nav;
}
.side {
  grid-area: sidebar;
}
.ad {
  grid-area: ad;
}
.main-footer {
  grid-area: footer;
}

这不会创建任何布局,但是这些项目现在有了我们可以用来创建布局的名称。保持在任何媒体查询之外,我们现在将为移动宽度设置布局。在这里,我们将所有内容都保留在源代码顺序中,尝试避免源代码和显示之间出现任何脱节,如指南 网格布局和可访问性 中所述。我们没有定义任何列或行轨道,但此布局指示单个列,并且将根据隐式网格中每个项目的需要创建行。

css
.wrapper {
  display: grid;
  gap: 20px;
  grid-template-areas:
    "header"
    "nav"
    "content"
    "sidebar"
    "ad"
    "footer";
}

在我们的移动布局就位后,我们现在可以继续添加一个媒体查询 来调整此布局,以适应具有足够空间来显示两列的更大屏幕。

css
@media (min-width: 500px) {
  .wrapper {
    grid-template-columns: 1fr 3fr;
    grid-template-areas:
      "header  header"
      "nav     nav"
      "sidebar content"
      "ad      footer";
  }
  nav ul {
    display: flex;
    justify-content: space-between;
  }
}

您可以在grid-template-areas 的值中看到布局正在形成。header 跨越两列轨道,nav 也是如此。在第三行轨道中,我们有 sidebar 以及 content。在第四行轨道中,我选择放置我的 ad 内容,因此它出现在侧边栏下方,然后 footer 在其旁边出现在内容下方。我们正在导航上使用 Flexbox 以一行间隔的方式显示它。

我们现在可以添加一个最终断点以切换到三列布局。

css
@media (min-width: 700px) {
  .wrapper {
    grid-template-columns: 1fr 4fr 1fr;
    grid-template-areas:
      "header header  header"
      "nav    content sidebar"
      "nav    content ad"
      "footer footer  footer";
  }
  nav ul {
    flex-direction: column;
  }
}

三列布局有两个 1fr 单位侧列和一个中间列,该列的轨道大小为 4fr。这意味着容器中的可用空间被分成 6 部分,并按比例分配到我们的三个轨道——侧列各占一部分,中心列占 4 部分。

在这个布局中,我们将在左侧列显示nav,以及content。在右侧列,我们有sidebar,并在其下方是广告(ad)。footer现在横跨布局底部。然后我使用弹性盒布局将导航显示为一列。

这是一个简单的示例,但演示了我们如何使用网格布局为不同的断点重新排列布局。特别是,我们根据不同的列设置,更改了ad块的位置。我发现这种命名区域的方法在原型设计阶段非常有用,可以轻松地调整元素的位置。即使由于访问您网站的浏览器,您无法完全依赖它在生产环境中使用,您也可以以这种方式开始使用网格进行原型设计。

灵活的 12 列布局

如果您一直在使用众多框架或网格系统之一,您可能习惯于在12列或16列的灵活网格上布局您的网站。我们可以使用CSS网格布局创建这种类型的系统。作为一个简单的示例,让我们创建一个12列的灵活网格,它具有12个1fr单元的列轨道,它们都具有名为col-start的起始线。这意味着我们将有十二条名为col-start的网格线。

css
.wrapper {
  display: grid;
  grid-template-columns: repeat(12, [col-start] 1fr);
  gap: 20px;
}

为了演示此网格系统的工作原理,我在包装器内部有四个子元素。

html
<div class="wrapper">
  <div class="item1">Start column line 1, span 3 column tracks.</div>
  <div class="item2">
    Start column line 6, span 4 column tracks. 2 row tracks.
  </div>
  <div class="item3">Start row 2 column line 2, span 2 column tracks.</div>
  <div class="item4">
    Start at column line 3, span to the end of the grid (-1).
  </div>
</div>

然后,我们可以使用命名行以及span关键字将这些元素放置在网格上。

css
.item1 {
  grid-column: col-start / span 3;
}
.item2 {
  grid-column: col-start 6 / span 4;
  grid-row: 1 / 3;
}
.item3 {
  grid-column: col-start 2 / span 2;
  grid-row: 2;
}
.item4 {
  grid-column: col-start 3 / -1;
  grid-row: 3;
}

命名行指南中所述,我们使用命名行来放置项目。由于我们有12条名称相同的线,因此我们使用名称,然后是线的索引。如果您愿意,也可以使用行索引本身,完全避免使用命名行。

我没有设置结束行号,而是选择使用span关键字说明此元素应跨越多少轨道。我喜欢这种方法,因为在使用多列布局系统时,我们通常从网格跨越的轨道数量方面考虑块,并针对不同的断点调整它。要查看块如何与轨道对齐,请使用Firefox网格检查器。它清楚地展示了我们的项目是如何放置的。

Showing the items placed on the grid with grid tracks highlighted.

网格布局的工作方式与您之前可能使用过的网格系统之间存在一些关键差异。如您所见,我们不需要添加任何标记来创建行,网格系统需要这样做以防止元素弹出到上一行。使用CSS网格布局,我们可以将内容放置到行中,如果一行为空,则不会有它们上升到上一行的危险。由于这种严格的列和行放置,我们还可以轻松地在布局中留出空白。我们也不需要特殊的类来拉动或推动内容,以将其缩进到网格中。我们只需要指定项目的起始行和结束行即可。

使用 12 列系统构建布局

要查看这种布局方法在实践中的工作原理,我们可以创建与使用grid-template-areas创建的相同布局,这次使用12列网格系统。让我们从与网格模板区域示例中使用的相同标记开始。

html
<div class="wrapper">
  <header class="main-head">The header</header>
  <nav class="main-nav">
    <ul>
      <li><a href="">Nav 1</a></li>
      <li><a href="">Nav 2</a></li>
      <li><a href="">Nav 3</a></li>
    </ul>
  </nav>
  <article class="content">
    <h1>Main article area</h1>
    <p>
      In this layout, we display the areas in source order for any screen less
      that 500 pixels wide. We go to a two column layout, and then to a three
      column layout by redefining the grid, and the placement of items on the
      grid.
    </p>
  </article>
  <aside class="side">Sidebar</aside>
  <div class="ad">Advertising</div>
  <footer class="main-footer">The footer</footer>
</div>

然后,我们可以像上面12列布局示例一样设置我们的网格。

css
.wrapper {
  display: grid;
  grid-template-columns: repeat(12, [col-start] 1fr);
  gap: 20px;
}

我们再次将其设置为响应式布局,但这次使用命名行。每个断点都将使用12列网格,但是项目跨越的轨道数量会根据屏幕大小而变化。

我们首先从移动端开始,对于最窄的屏幕,我们只希望项目保持源顺序,并且全部跨越整个网格。

css
.wrapper > * {
  grid-column: col-start / span 12;
}

在下一个断点,我们希望切换到两列布局。我们的标题和导航仍然跨越整个网格,因此我们不需要为它们指定任何定位。侧边栏从名为col-start的第一列线开始,跨越3条线。它位于第3行之后,因为标题和导航位于前两行轨道中。

广告面板位于侧边栏下方,因此从网格第4行开始。然后我们有内容和页脚从col-start 4开始,跨越9个轨道,将它们带到网格的末尾。

css
@media (min-width: 500px) {
  .side {
    grid-column: col-start / span 3;
    grid-row: 3;
  }
  .ad {
    grid-column: col-start / span 3;
    grid-row: 4;
  }
  .content,
  .main-footer {
    grid-column: col-start 4 / span 9;
  }
  nav ul {
    display: flex;
    justify-content: space-between;
  }
}

最后,我们进入此布局的三列版本。标题继续跨越整个网格,但现在导航向下移动成为第一个侧边栏,然后是内容,然后是侧边栏。页脚现在也跨越整个布局。

css
@media (min-width: 700px) {
  .main-nav {
    grid-column: col-start / span 2;
    grid-row: 2 / 4;
  }
  .content {
    grid-column: col-start 3 / span 8;
    grid-row: 2 / 4;
  }
  .side {
    grid-column: col-start 11 / span 2;
    grid-row: 2;
  }
  .ad {
    grid-column: col-start 11 / span 2;
    grid-row: 3;
  }
  .main-footer {
    grid-column: col-start / span 12;
  }
  nav ul {
    flex-direction: column;
  }
}

同样,网格检查器有助于我们了解布局是如何形成的。

Showing the layout with grid tracks highlighted by the grid inspector.

在我们创建此布局时需要注意的一点是,我们不需要在每个断点处显式地定位网格上的每个元素。我们能够继承为早期断点设置的放置——“移动优先”工作的一个优势。我们还可以利用网格自动放置。通过将元素保持在逻辑顺序中,自动放置为我们做了很多工作,将项目放置到网格上。在本指南中的最后一个示例中,我们将创建一个完全依赖于自动放置的布局。

具有自动放置的产品列表

许多布局本质上是一组“卡片”——产品列表、图片库等。网格可以非常轻松地以响应式的方式创建这些列表,而无需添加媒体查询来实现这一点。在下一个示例中,我将结合使用CSS网格和弹性盒布局来创建一个简单的产品列表布局。

我的列表的标记是一个无序的项目列表。每个项目包含一个标题、一些高度不同的文本和一个号召性用语链接。

html
<ul class="listing">
  <li>
    <h2>Item One</h2>
    <div class="body">
      <p>The content of this listing item goes here.</p>
    </div>
    <div class="cta">
      <a href="">Call to action!</a>
    </div>
  </li>
  <li>
    <h2>Item Two</h2>
    <div class="body">
      <p>The content of this listing item goes here.</p>
    </div>
    <div class="cta">
      <a href="">Call to action!</a>
    </div>
  </li>
  <li class="wide">
    <h2>Item Three</h2>
    <div class="body">
      <p>The content of this listing item goes here.</p>
      <p>This one has more text than the other items.</p>
      <p>Quite a lot more</p>
      <p>Perhaps we could do something different with it?</p>
    </div>
    <div class="cta">
      <a href="">Call to action!</a>
    </div>
  </li>
  <li>
    <h2>Item Four</h2>
    <div class="body">
      <p>The content of this listing item goes here.</p>
    </div>
    <div class="cta">
      <a href="">Call to action!</a>
    </div>
  </li>
  <li>
    <h2>Item Five</h2>
    <div class="body">
      <p>The content of this listing item goes here.</p>
    </div>
    <div class="cta">
      <a href="">Call to action!</a>
    </div>
  </li>
</ul>

我们将创建一个具有灵活数量的灵活列的网格。我希望它们永远不会小于200像素,然后平均分配任何剩余的可用空间——因此我们始终获得等宽的列轨道。我们通过在轨道大小的重复表示法中使用minmax()函数来实现这一点。

css
.listing {
  list-style: none;
  margin: 2em;
  display: grid;
  gap: 20px;
  grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
}

一旦我们添加了此CSS,项目就开始作为网格布局。如果我们缩小或放大窗口,列轨道的数量会发生变化——而无需我们使用媒体查询添加断点并重新定义网格。

然后,我们可以使用一些弹性盒布局来整理框的内部内容。我将列表项设置为display: flex并将flex-direction设置为column。然后,我们可以在.cta上使用自动边距将此栏推到底部。

css
.listing li {
  border: 1px solid #ffe066;
  border-radius: 5px;
  display: flex;
  flex-direction: column;
}
.listing .cta {
  margin-top: auto;
  border-top: 1px solid #ffe066;
  padding: 10px;
  text-align: center;
}
.listing .body {
  padding: 10px;
}

这确实是有人使用弹性盒而不是网格的关键原因之一,如果他/她只是在一维中对齐或分配某些内容,那就是弹性盒的用例。

使用 dense 关键字防止出现间隙

现在看起来已经相当完整了,但是我们有时会有这些卡片,它们包含的内容比其他卡片多得多。导致它们跨越两条轨道可能会很好,这样它们就不会那么高了。我的较大项目上有一个名为wide的类,我们添加了一个规则grid-column-end,其值为span 2。现在,当网格遇到此项目时,它将为其分配两条轨道。在某些断点处,这意味着网格中会出现间隙——没有空间来布置两轨项目。

The layout has gaps as there is not space to lay out a two track item.

我们可以通过在网格容器上设置grid-auto-flow: dense来使网格填充这些间隙。但是,在执行此操作时要小心,因为它确实会将项目从其逻辑源顺序中移除。仅当您的项目没有固定的顺序时才应执行此操作,并注意选项卡顺序遵循源代码而不是重新排序显示的问题

css
.listing {
  list-style: none;
  margin: 2em;
  display: grid;
  gap: 20px;
  grid-auto-flow: dense;
  grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
}
.listing .wide {
  grid-column-end: span 2;
}

这种对某些项目应用某些规则的自动放置技术非常有用,可以帮助您处理例如由CMS输出的内容,在这些内容中,您有重复的项目,并且可以在将其呈现到HTML中时可能为某些项目添加一个类。

进一步探索

学习使用网格布局的最佳方法是继续构建像我们在这里介绍的示例。选择您通常使用所选框架或使用浮动构建的内容,看看您是否可以使用网格构建它。不要忘记查找使用当前方法无法构建的示例。这可能意味着从杂志或其他非网络来源获取灵感。网格布局打开了我们以前没有的可能性,我们不需要局限于相同的旧布局来使用它。