使用网格实现常见布局

为了总结这篇CSS 网格布局指南,我们将介绍几种不同的布局,展示一些在使用网格布局进行设计时可以使用的技巧。我们将看一个使用 grid-template-areas 的示例,一个 12 列的弹性网格系统,以及一个使用自动放置的产品列表。正如你从这些示例中看到的,使用 CSS 网格布局通常有不止一种方法来获得你想要的结果。请选择对你正在解决的问题和需要实现的设计最有帮助的方法。

使用 grid-template-areas 实现 1 到 3 列的响应式流体布局

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

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

我们将使用在网格模板区域指南中学到的命名模板区域来创建这个布局。

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

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";
}

在移动端布局就位后,我们现在可以继续添加一个 @media 查询,以便将此布局调整到具有足够空间显示两列的更大屏幕上。

css
@media (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 也是如此。在第三个行轨道中,我们将 sidebarcontent 并排放置。我们将 ad 内容放在第四个行轨道中,使其出现在侧边栏下方。footer 在它旁边,位于内容下方。我们对导航使用 CSS 弹性盒布局,以便在一行中均匀地分布导航项目。

我们现在可以为能够显示三列布局的更宽屏幕添加最后一个断点。

css
@media (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。这意味着容器中的可用空间被分成六份,并按比例分配给我们的三个轨道——侧边栏各占一份,中间列占四份。

在这个布局中,我们将 nav 显示在左列,与 content 并排。右列有 sidebar,其下方是广告(ad)。footer 现在跨越了整个布局的底部。我们再次使用 Flexbox 来显示导航,但这次我们将其显示为一列而不是一行。

这个基本示例演示了如何在不同断点之间重新排列网格布局。特别是,我们根据不同的列设置,适当地更改了 ad 块的位置。这种命名区域的方法非常有用,尤其是在原型设计阶段。你可能会发现在调整网格上元素位置时,使用名称比使用数字更容易。

一个灵活的 12 列布局

CSS 框架和网格系统通常使用 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 关键字定义该元素应跨越多少个轨道。在使用多列布局系统时,对于那些习惯于根据网格轨道跨越数量来考虑块,然后根据不同断点进行调整的人来说,这种方法可能更直观。要查看块如何与轨道对齐,请使用浏览器开发者工具中的网格检查器;它可能会清楚地展示项目是如何放置的。

Showing the items placed on the grid with grid tracks highlighted in Firefox developer tools.

我们不需要添加任何标记来创建行。CSS 框架网格系统通常这样做是为了防止在不支持 CSS 网格布局的浏览器中,元素会跳到上一行。然而,这一点现在已经没有意义了——所有现代浏览器都早已支持 CSS 网格布局。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 条行线之后,因为页眉和导航在前两个行轨道中。

ad 面板位于侧边栏下方,从网格第 4 行线开始。然后我们有内容和页脚,从 col-start 4 开始并跨越九个轨道,将两者都带到网格的末尾。

css
@media (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 (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 网格和 Flexbox 布局来制作一个基本的产品列表布局。

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

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 像素宽,并平均共享任何可用的剩余空间——这样我们总是能得到等宽的列轨道。我们通过在轨道大小的 repeat() 表示法中使用 minmax() 函数来实现这一点。

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

当我们添加此 CSS 时,项目将被布局为网格。如果我们将窗口变小或变宽,列轨道的数量会发生变化——无需媒体查询添加断点,也无需重新定义网格。

我们可以使用一点 Flexbox 来整理盒子的内部。我们将列表项设置为 display: flex,并将 flex-direction 设置为 column。然后我们可以对 .cta 使用 auto margin 将这个栏推到盒子的底部。

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

这是使用 Flexbox 而不是 CSS 网格布局的关键原因之一。如果你正在单个维度上对齐或分布内容,那就是 Flexbox 的用例。

使用 dense 关键字防止间隙

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

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

我们可以通过在网格容器上设置 grid-auto-flow: dense 来让网格回填这些间隙。这样做时要小心,因为它可能导致项目脱离其逻辑源顺序。只有当你的项目没有固定顺序时才应该这样做。此外,请注意由于 Tab 键顺序遵循源顺序而非重新排序后的显示顺序所导致的无障碍和重新排序问题

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 输出,其中你有重复的项目,并且可以使用结构伪类来定位它们。

进一步探索

CSS 网格布局提供了如此多的可能性。学习使用网格布局的最佳方法是继续构建像我们这里所介绍的示例。从你喜欢的响应式网站中挑选一个布局,看看你是否可以使用网格来构建它。你甚至可以从杂志或其他非 Web 来源中获取灵感。