图解CSS网格

272 阅读13分钟

写在前面的话

本文为译文,因为笔者也是最近开始尝试翻译文章,如有翻译地不合理或者不正确的地方,欢迎指出。原文地址:CSS Grid: illustrated introduction

正文开始

我还记得第一次学习CSS知识的时候,我是如何兴奋地学习使用float和inline展示元素在想要的布局位置。我想知道如果当时有一个二维布局系统,我会作何反应。事实上,即使到现在我还是感到兴奋,因为我们编写CSS的方式以及我们编写标记语言的方式改变了一切。使用CSS网格,使得我们构建响应式的、动态的、源码顺序独立的布局比之前更加容易。

注意,源码顺序独立只是根据字面翻译,我理解的含义是编写的html标签的顺序跟页面上展示标签的顺序无关,我们可以通过CSS 网格样式任意修改元素在页面上的展示顺序。

在本文中,我们将学习使用所有的CSS网格知识来构建简单的和一些不太容易的布局。我们将定义一切CSS网格知识,然后接着将挖掘得更深一点,看看我们可以使用CSS网格实现什么样的布局。话虽如此,如果你已经准备好学习一种新的布局方式,吃下红色药丸,我会告诉你兔子洞到底有多深。

matrix red pill choice

1.在我们开始之前

在我们开始学习之前,我想提出一些或许你也可能有的一些顾虑,同时确保我们熟悉CSS网格的基础知识和术语。

Q & A

Q:CSS网格会替代flex-box布局吗?

A:当然,CSS网格不会替代flex-box,这两种布局是针对不同的使用场景。事实上,它们还可以很好地搭配使用。我们可以在CSS网格布局中使用flex布局,反之亦然。

Q:CSS网格布局和flex-box布局有什么不同?

A:它们有很多不同点,最主要的区别就是flex-box是一种一维的布局系统,而CSS网格是一种二维的布局系统。让我们看看下图:

grid image

图一

Q:为什么不使用Bootstrap呢?

A:我认为最好的回答是引用Jen Simmons的表述:

使用CSS Grid越多,使我更加确信,在其上添加抽象层没有任何好处,CSS网格就是是嵌入浏览器的CSS布局框架。

Q:CSS 网格为用于生产环境准备好了吗?

A:这取决于你的网站,你的网站是否要兼容IE,Opera mini,Blackberry browser,Baidu mobile等浏览器?如果回答是否定,你可以直接用于生产环境。如果回答是肯定,那么你可以在支持它的浏览器(不需要添加前缀的浏览器占比:91.61%)使用它通过编写@supportsCSS规则:

@supports (display: grid) {
  div {
    display: grid;
  }
}

基础知识

基本上,一个网格布局中可以分解为两个元素:容器(grid container)和项目(grid-items)。

grid image

图二

从图二中我们可以看到,网格容器就是一个行和列的集合。一行就是两根连续的水平线之间的空间,一列就是两根连续的竖线之间的空间。一行被称为一个轨道(track),对于列也一样。因此网格轨道就是两根平行的网格线之间的空间。

每一个轨道可以有一个或者多个网格单元。cell就是最小的、最基本的网格单元,它是四根相交的网格线之间的空间。如果我们把多个网格单元组合起来就形成了一个网格区域,值得一提的是网格区域必须是一个矩形区域,例如我们不能产生一个T形状的网格区域。

网格线从1开始到任何编号你可以显示或者隐式的定义,最后的网格线编号可以被设为-1,而在它之前的网格线编号为-2,依此类推,这个在稍后的例子中会很方便。在图二中列网格线的编号是从1增加6(或者是从-6到-1),行的网格线编号是从1增加到5(或者是从-5到-1)。

网格线的编号可以被显示的定义如果你在CSS样式中明确设置,它也可以被隐式地设置如果它是被浏览器动态地生成。

最后要补充地是,网格单元可以通过空格和间距分开,这些间距被称为“槽”(gutters),但是我们一般把它们当成间距。

2.CSS网格的基本属性

好了,这样我们可以开始实现一些网格布局。首先我们讨论网格容器可以使用的所有属性,然后再介绍一下关于网格项(item)的一些属性。

在本节中,让我们考虑如下模板:

<div class="grid-container">
    <div class="grid-item">grid item 1</div>
    <div class="grid-item">grid item 2</div>
    <div class="grid-item">grid item 3</div>
    <div class="grid-item">grid item 4</div>
    <div class="grid-item">grid item 5</div>
    <div class="grid-item">grid item 6</div>
    <div class="grid-item">grid item 7</div>
    <div class="grid-item">grid item 8</div>
    <div class="grid-item">grid item 9</div>
</div>

网格容器

Display

CSS网格是使用display属性的grid值定义的 ,因此,使用上面的模板定义网格布局,我们应该这样做:

.grid-containter {
  display: grid;
}

列 & 行

我们可以通过使用grid-template-rowsgrid-template-columns属性定义网格的行和列:

.grid-container {
  grid-template-columns:  1fr 1fr 1fr 1fr;
  grid-template-rows:  1fr auto 2fr;
}

或者我们可以使用grid-template属性首先定义grid-template-rows然后定义grid-template-columns(使用斜杆分隔):

.grid-container {
  grid-template:  1fr auto 2fr  / 1fr 1fr 1fr 1fr;
}

顺便提一下,fr是一个分数单位,因此1fr就是可用空间的一个部分。

Repeat function

repeat函数表示轨道(track)列表的一个重复片段,因此我们可以得到和上面一样的模板通过如下代码:

.grid-container {
  grid-template:  1fr auto 2fr / repeat(4, 1fr);
}

阅读文档repeat 可以了解怎么使用auto-fitauto-fill动态地添加轨道(track)。

Minmax function

minmax函数定义了一个尺寸的范围,该范围大于或者等于最小值,小于或者等于最大值,我们可以将该函数和repeat一起使用:

.grid-container {
  grid-columns:  repeat(3, minmax(100px, 1fr));
}

间距

我们可以使用row-gap给行与行之间设置间距,同样也可以使用column-gap给列与列设置间距:

.grid-container {
  row-gap: 5px;
  column-gap: 10px;
}

我们也可以使用gap属性先定义行间距然后定义列间距:

.grid-container {
  gap: 5px 10px;
}

如果row-gapcolumn-gap值是一样的,我们只需要指定一个值。

网格项

指定一个网格项在网格布局中的开始和结束位置,我们基本上使用四个属性,让我们先看下它们的定义。

属性 定义
grid-row-start grid-row-start属性通过提供一个行、一个span或者什么都不提供来指定网格行在网格中的开始位置(自动)
grid-row-end grid-row-end属性通过提供一个行、一个span或者什么都不提供来指定网格行在网格中的结束位置(自动)
grid-column-start grid-column-start属性通过提供一个行、一个span或者什么都不提供来指定网格列在网格中的开始位置(自动)
grid-column-end grid-column-end属性通过提供一个行、一个span或者什么都不提供来指定网格列在网格中的结束位置(自动)

或者我们也可以使用这些属性的简写形式:

属性 定义
grid-row grid-row属性是grid-row-startgrid-row-end的简写属性,用于指定网格行中网格项的大小和位置
grid-column grid-column属性是grid-column-startgrid-column-end的简写属性,用来指定网格列中网格项的大小和位置

基本模板间距

考虑一下我们在本节开始时使用的标记,假设我们想要第三个网格项占用四个单元格而不是一个(我们希望它跨越两个网格列和两个网格行),效果就像图三一样,我们该怎么做了?

template spacing

图三

我们可以像这样实现:

// Grid container
.grid-container {
  display: grid;
  gap: 10px;
  grid-template-columns: repeat(4, 1fr);
  grid-template-rows: repeat(3, 1fr);
}

// Grid item (third)
.grid-container .grid-item:nth-child(3) {
  grid-column-start: 1;
  grid-column-end: 3;
  grid-row-start: 1;
  grid-row-end: 3;
  // or
  grid-column: 1 / 3;
  grid-row: 1 / 3;
  // or
  grid-column: 1 / span 2;
  grid-row: 1 / span 2;
  // or
  grid-column: -5 / span 2; // because we have 4 columns
  grid-row: -4 / span 2; // because we have 3 rows
}

注意,第三个网格项其实就是图三中的第一个,这与CSS网格可以(第一次)拥有源码顺序独立性有关。当我们讨论grid-auto-flow的时候,我们还会讲到这个知识点。

如果你想玩它,探索不同的解决方案,点击这里

3.高级模板

还有一些更高级的属性可以帮助您按照自己的需要调整模板。在这小节,我们将学习这些属性并知道如何在CSS中使用它们。

在这个小节,我们考虑如下模板:

 <div class="grid-container">
   <div class="grid-item header">Header</div>
   <div class="grid-item content">Content</div>
   <div class="grid-item navbar">Navbar</div>
   <div class="grid-item meta">Meta</div>
   <div class="grid-item footer">Footer</div>
 </div>

使用我们前面学过的知识,我们可以使用如下的CSS样式使得它看起来像一个基本的网站布局:

.grid-container {
  grid-template: repeat(6, 1fr) / repeat(12, 1fr);// rows then columns
}
.grid-container .header {
  grid-column: 1 / -1;  
  grid-row: 1 / 2;
}
.grid-container .navbar {
  grid-column: 1 / 2;  
  grid-row: 2 / -1;
}
.grid-container .content {
  grid-column: 2 / -1;  
  grid-row: 2 / -2;
}
.grid-container .footer {
  grid-column: 2 / -1;  
  grid-row: -2 / -1;
}
.grid-container .meta {
  grid-column: -3 / -1;  
  grid-row: 2 / 4;
}

basic layout

图四

现在假设我们想要导航条(在右边)宽一点,目前,它跨越了一列,但是我们想要它跨越两列。为了实现这个效果,我们需要改变.navbar的位置,同时也要改变.content.footer的位置,因为当前.navbar是从列1到列2,而.footer.content是从列2一直到最后。

如果每次都要修改元素的位置,那就太单调了。如果有一种办法来告诉CSS网格自动为我们去做这件事,那就太好了。当然,不止有一种方法可以实现,至少有两种方法。

命名行

第一种解决方案就是命名特定的行,然后我们可以用它的别名而不是它的编号来引用它,让我们尝试实现它。

.grid-container {
  grid-template-rows: repeat(6, 1fr);
  grid-template-columns: 1fr 1fr [content-start navbar-end] repeat(10, 1fr);
}

在上面的代码中,我们使用包含别名的简单方括号为第三行命名(单一的行可以有多个别名)。然后我们修改前面提到的元素的CSS代码:

.grid-container .navbar {
  grid-column: 1 / navbar-end;  
  grid-row: 2 / -1;
}
.grid-container .content {
  grid-column: content-start / -1;  
  grid-row: 2 / -2;
}
.grid-container .footer {
  grid-column: content-start / -1;  
  grid-row: -2 / -1;
}

实现的效果像下面这样:

extended navbar

图五

尝试对其它的行实现相同的效果(前面的:header-row-end / content-row-start)可以点击下面的代码片段

元素的模板区域

第二种解决方案是使用模板区域,grid-template-areas属性用来指定命名的网格区域。这个属性有个奇怪的CSS语法,但我们像这样使用它:

.grid-container {
  grid-template-areas:
    'h h h h h h h h h h h h'
    'n n c c c c c c c c c c'
    'n n c c c c c c c c c c'
    'n n c c c c c c c c c c'
    'n n c c c c c c c c c c'
    'n n f f f f f f f f f f';
}
.grid-container .navbar {
  grid-area: n;
}
.grid-container .content {
  grid-area: c;
}
.grid-container .footer {
  grid-area: f;
}
.grid-container .header {
  grid-area: h;
}
.grid-container .meta {
  grid-column: -3 / -1;  
  grid-row: 2 / 4;
}

我们使用grid-template-areas定义网格容器区域,然后使用grid-area将网格项放置到需要的网格区域。值得注意的是,所有的区域必须是矩形。

注意,我们没有为.meta元素使用grid-area,这是因为,目前没有可以使用这种方式来叠加元素的方法,至少我不知道。

你可以继续玩它,可用的代码点击这里

4.隐式行和网格流

考虑如下代码:

<div class="grid-container">
  <div class="grid-item">1</div>
  <div class="grid-item">2</div>
  <div class="grid-item">3</div>
  <div class="grid-item">4</div>
  <div class="grid-item">5</div>
  <div class="grid-item">6</div>
</div>
<style>
.grid-container {
  grid-template-columns: repeat(3, minmax(100px, 1fr));
  grid-template-rows: 80px;
}
.grid-container .grid-item:nth-child(2) {
  grid-row: span 2;
}
.grid-container .grid-item:nth-child(3) {
  grid-column: span 3;
}
</style>

我们有一个三列的网格,我们想让第二个网格项跨越两行,第三个跨越三列,结果如下图:

bad grid

图六

这看起来很糟糕,所以这里发生了什么?首先,第二个元素比一个元素稍微高了一点,因为我们想要它高一倍,但是看起来根本不像是高了一倍的高度。同样,从3到6的网格项也没有第一个高。

隐式行

这与我们显示的设置第一行CSS样式有关:grid-template-rows: 80px,而其它的行被隐式地创建了,因此,第二行几乎是不可见的,因为它是空的,而其它行的大小取决于它们内容所需要的。

我们可以像下面代码这样使用grid-auto-rows属性设置隐式创建的行高度来修复这个问题:

.grid-container {
  grid-template-columns: repeat(4, minmax(100px, 1fr));
  grid-template-rows: 80px;
  grid-auto-rows: 100px; 
} 

这样看起来就像下图一样:

better grid, but still

图七

这样看起来好一点了,但是我们仍然可以做得更好。注意这些空白的空间,为什么我们没有使用它们放置网格4,5,6项,为了做到这样,我们可以使用grid-auto-flow

网格流

grid-auto-flow属性控制自动放置算法的工作方式,准确地指定自动放置的网格项如何流入网格,它可以有多个参数(你可以点击这里了解更多),但是在这里,我们关心的只有一个:dense。这个值告诉浏览器将网格项放置在任何足够大的空间:

.grid-container {
  grid-auto-flow: dense; // default is row
}

这样我们的网格看起来就很漂亮了:

good grid

图八

结论

讲到这里,我们需要处理大量的信息,但是有了这些知识,我们涵盖了大量的CSS网格属性,因此我们可以愉快地在应用中使用CSS网格布局。这篇文章是一系列文章中的第一篇,在下一篇中,我们将使用网格实现三个实际的例子,因此跟上节奏。

我希望你一如既往地从文章中学到了有用的知识,快乐编程!