CSS 布局的未来:网格布局

在本文中,我们将探讨 CSS 网格布局的奇妙世界,这是一个相对较新的 W3C 规范,它在一些浏览器中已经部分开始实现。

但在深入探讨这项新的 CSS 技术及其使用方法之前,让我们先快速回顾一下网格理论。

我对网格理论的简短介绍

我不是设计师,在偶然发现 CSS 网格布局规范之前,我对网格理论了解也不多,所以不要轻信我的说法,自己去查阅资料。但如果你不想查阅,这里是我的总结。

本质上,网格是一组无形的垂直和水平线条,用于定位网页、杂志或报纸的各种设计和内容元素。网格的目的是作为放置各种内容元素的基准,并确保这些元素对齐并均匀间隔。即使不可见,网格也为观看者提供了一种以视觉方式浏览内容的方法。

那么 CSS 与此有何关系呢?

CSS 正在获得一组全新的属性,这些属性结合使用时,可以让你为 Web 内容定义网格。这些属性在 CSS 网格布局规范 中定义。

CSS 并不以其处理复杂布局的能力而闻名。虽然这在 CSS 首次问世时可能不是问题,但随着时间的推移,它已经发展成为一个相当普遍的问题,因为各种复杂布局无法轻松实现。人们通过使用浮动或绝对定位以及各种 CSS 布局框架,创造性地解决了缺乏支持的问题。

所以,是时候让 CSS 获得一些合适的布局解决方案来支持当今的用例了。

我可以用它吗?

是的,你现在就可以开始使用它进行实验和学习。现在使用它来创建商业网站还为时尚早。

Chrome 已经实现了足够多的规范,可以用来玩网格。开启实验性 Web 平台功能标志以使其工作。Chrome 的实现目前是最新的,它与 CSS 网格布局规范的最新更改保持一致。

Internet Explorer 拥有第一个 CSS 网格实现(可以追溯到 IE10),但规范自从它被添加以来已经发生了演变,因此它不再完全兼容最新规范。他们很快就会更新其实现。

Firefox 也已 开始实现 它,所以你不久就可以在这个浏览器中使用它。

最后,一个 polyfill 存在,因此你没有理由不开始尝试。(我强烈建议你这样做!)。但是,你可能不应该在生产环境中使用 polyfill。

那么什么是 CSS 网格布局呢?

从本质上说,CSS 中的网格布局是一组垂直和水平线条,这些线条定义了可以将内容任意定位的单元格。
所以它在某种程度上看起来像一个表格,除了我们稍后会看到的一些关键区别。

1 zcOcwuBtMoBaUfHHAJPNyg

上图显示了网格的构建块。

  • **线条**:在这种情况下,有 4 条垂直线和 3 条水平线。线条从 1 开始编号。垂直线从左到右显示,但这取决于书写方向。线条可以选择性地命名,这有助于在 CSS 中引用它们,我们稍后会看到。
  • **轨道**:轨道只是两条平行线之间的空间。因此,在上面的示例中,有 3 条垂直轨道和 2 条水平轨道。线条有助于指示内容的开始和结束位置,但轨道是内容最终所在的地方。
  • **单元格**:单元格是水平轨道和垂直轨道相遇的地方。在上图中,只有一个单元格被突出显示,但网格中有 6 个单元格。
  • **区域**:区域是一个矩形形状,可以跨越任意数量的单元格。区域就像线条一样,可以命名。在上图中的网格中,例如,我们可以定义区域 A、B 和 C,如下所示。

1 zcOcwuBtMoBaUfHHAJPNyg

现在我们知道了这些简单的定义,让我们看看是什么让网格如此强大。

CSS 网格的一个关键优势是它们强制执行布局和标记的真正分离。

事实上,网格本身完全在纯 CSS 中定义。这意味着,除了应用网格的父 HTML 元素之外,不需要为列、行、单元格或区域定义任何额外的元素。

当你想到这一点时,这是一个非常有趣的属性。它的一方面是页面上元素的视觉顺序与标记中元素的顺序分离。这一点很重要,因为在页面上,源顺序用于语音和选项卡导航,因此 CSS 网格允许你优化标记以实现可访问性,而不会影响操纵视觉结果的能力。另一点是,标记将更轻巧、更容易理解,因此也更容易维护。

但更重要的是,它为我们提供了一个非常强大的工具来将内容与布局分离,有效地将它们解耦,从而可以更改其中一个而不影响或破坏另一个。
作为设计师,你可以轻松地尝试新的布局,而无需更改除 CSS 之外的任何内容,只要你的新布局提供内容使用的预期线条和区域即可。
作为开发者,你只需使用编号或命名的线条和区域来定位内容在网格中的位置。

想象一下以下简单的网格布局。

1 zcOcwuBtMoBaUfHHAJPNyg

在这个布局中,已经定义了命名的区域,允许通过简单地引用名称来定位内容。这意味着,我们不仅可以在未来相对轻松地更改此布局(只要我们维护命名的区域,这里的命名区域在某种程度上充当布局的公共 API),而且还可以使用媒体查询来动态地更改此布局。记住,整个布局都是在 CSS 中定义的,因此媒体查询可以很好地与网格布局配合使用。

例如,使用媒体查询,之前的布局可以在较小的屏幕上切换到类似于以下的内容。

1 zcOcwuBtMoBaUfHHAJPNyg

理论够多了,让我们看看网格布局实际上是如何使用 CSS 定义的。

使用 CSS 创建网格

定义网格只需要一个 HTML 元素:**网格容器**。这是将应用布局的元素。任何作为网格容器子元素的 HTML 元素都是**网格项**。网格项是放置在网格上的元素。

将元素设置为网格容器就像
<pre>.container { display: grid; }</pre>
但是,仅执行此操作还不够,我们还需要定义此网格的外观,它有多少列和行,以及它们有多大。
这可以使用网格模板来完成,如以下示例所示
<pre>.container {
display: grid;
grid-template-rows: 200px 100px;
grid-template-columns: repeat(4, 100px);
}</pre>

在上面的示例中,我们显式地定义了一个具有 2 行和 4 列的网格。请注意,repeat() 函数在这里如何使用以避免重复轨道的片段 4 次,这等效于 100px 100px 100px 100px

每个定义的轨道都指定了一个大小,因此我们最终得到一个看起来像这样的网格结构。

1 zcOcwuBtMoBaUfHHAJPNyg

但是网格也可以像这样隐式定义
<pre>.container {
display: grid;
grid-template-rows: auto;
grid-template-columns: repeat(4, 100px);
}</pre>

在这种情况下,浏览器将根据需要不断添加行以适应内容。

现在让我们添加一些内容。正如我们之前所说,我们只需要两级元素,容器和项,所以让我们使用这个
<pre><div class="container">
<div class="item1">item 1</div>
<div class="item2">item 2</div>
<div class="item3">item 3</div>
<div class="item4">item 4</div>
<div class="item5">item 5</div>
</div></pre>

如果没有具体的 CSS 代码来定位项,将导致以下排列。

1 zcOcwuBtMoBaUfHHAJPNyg

正如你在上面看到的,由于我们没有定义这些项应该在网格中的哪个位置,浏览器通过在第一行填满之前将每个项放置在一个单元格中,自动地定位了它们,然后它决定将剩余的项放置在下一行中。

规范定义了一个算法,用于在未指定位置的情况下自动将项放置在网格中。这在你有许多项或动态数量的项并且不想或不能为所有项定义位置时有时很有用。

让我们看看如何将我们的项定位在网格中。
<pre>.item1 {
grid-row-start: 1;
grid-row-end: 2;
grid-column-start: 1;
grid-column-end: 3;
}
.item2 {
grid-row-start: 1;
grid-row-end: 2;
grid-column-start: 3;
grid-column-end: 5;
}</pre>

我们已经定义了前两个项目的定位,这意味着其他项目仍然会由浏览器自动定位,如下所示

1 zcOcwuBtMoBaUfHHAJPNyg

上面的示例使用 **基于线的定位** ,它使用线的数字索引来定位项目。项目 1 和 2 被定义为定位在水平线 1 和 2 之间,以及垂直线 1 和 3 以及 3 和 5 之间。

如果我们继续添加项目,但没有定义任何网格单元格来接收它们,会发生什么?

1 zcOcwuBtMoBaUfHHAJPNyg

网格会根据需要继续添加行和列。请注意,这些新轨道的大小将取决于内容的大小。

所以这一切都很顺利,但是当谈到 CSS 网格时,真正有用的一件事是网格区域,就像我们之前看到的那样。让我们看看如何在 CSS 中使用网格区域。

首先,您需要定义网格容器及其轨道和区域
<pre>.container {
display: grid;
grid-template-rows: 100px auto 100px;
grid-template-columns: 100px auto;
grid-template-areas
"header header"
"sidebar content"
"footer footer";
}</pre>

在这个示例中,我们定义了 3 行,顶部和底部行的高度为 100px,而中间行将只适应其内容。我们还定义了 2 列,左列宽度为 100px,右列适应其内容。

我们还引入了 `grid-template-areas` 属性,它乍一看可能很奇怪,但当你意识到它只是一个带有名称的网格表示时,它实际上很简单。让我们解释一下。

我们说过我们有 3 行和 2 列,对吧?让我们用 ascii 艺术来表示它们,就像这样
<pre>+---+---+
| . | . |
+---+---+
| . | . |
+---+---+
| . | . |
+---+---+</pre>

每个点都是一个可以定义区域的单元格。所以让我们为一个典型的网站布局定义区域
<pre>+--------+--------+
| header | header |
+--------+--------+
| sidebar| content|
+--------+--------+
| footer | footer |
+--------+--------+</pre>

我们希望页眉和页脚跨越整个宽度,所以我们在两列中重复它们。
现在,如果我们只删除所有无用的 ascii 艺术风格边框并将每行包含在双引号中,我们将得到如下内容
<pre>"header header"
"sidebar content"
"footer footer"</pre>

这正是使用 `grid-template-areas` 属性在 CSS 中定义网格区域的方式。将它想象成 ascii 艺术中网格的二维表示,并将区域名称对齐,这有助于理解它。

当一个单元格未被区域覆盖时,您也可以使用点 '。' 。这是一个例子
<pre>.container {
display: grid;
grid-template-rows: repeat(5, 100px);
grid-template-columns: repeat(5, 100px);
grid-template-areas
". . . . ."
". . . . ."
". . a . ."
". . . . ."
". . . . .";
}</pre>

在这个示例中,有 25 个单元格和 1 个区域,该区域被定义为仅占用中心单元格。

现在,回到之前的页眉、侧边栏、内容、页脚示例,让我们为它添加一些标记
<pre><div class="container">
<div class="header">header</div>
<div class="sidebar">sidebar</div>
<div class="footer">footer</div>
<div class="content">
<p>Lorem ipsum dolor sit amet...</p>
</div>
</div></pre>

现在需要做的最后一件事是将每个网格项目定位在正确的命名区域中(我们在这里方便地使用了与区域名称相对应的类名)
<pre>.header { grid-area: header; }
.sidebar { grid-area: sidebar; }
.footer { grid-area: footer; }
.content { grid-area: content; }</pre>

这将产生类似于以下内容的结果

1 zcOcwuBtMoBaUfHHAJPNyg

现在,如果我们想在任何时候更改此布局,或者通过媒体查询使它适应可变的屏幕尺寸,我们只需要更改网格声明。也许变成这样
<pre>.container {
display: grid;
grid-template-rows: auto;
grid-template-columns: 100%;
grid-template-areas
"header"
"sidebar"
"content"
"footer";
}</pre>

无需对 CSS 或标记进行任何其他更改,我们已经产生了以下结果

1 zcOcwuBtMoBaUfHHAJPNyg

我们甚至可以完全重新排列网格中项目的顺序,而无需更改标记中的顺序。

现实世界中的 CSS 网格

到目前为止,我们只看到了网格的简单示例,但使用网格系统的网站通常具有更复杂的网格结构。对于此类网站来说,基于 12 列,每列之间用沟槽(用于间隔事物的更窄的列)分隔并不罕见。

dribbble.com 为例,这个设计展示网站使用网格来显示用户上传的设计。这里网格非常容易看到,因为每个设计正好占用一个单元格(不包括沟槽行和列)

Dribbble 的网格布局也是响应式的,因此,如果视窗大小发生变化,列的大小和数量会逐渐变化,如以下动画所示

使用 CSS 网格布局实现附带的新属性,我们可以尝试创建类似于 *dribbble* 的布局。为了增加难度,让我们尝试在第二行插入一段文本内容,并使该内容跨越整行。

首先,我们的标记将类似于以下内容
<pre><ul class="dribbbles">
<li class="dribbble">...</li>
<li class="dribbble">...</li>
...
<li class="dribbble">...</li>
<li class="advert">Some text ....</li>
</ul></pre>

这里,`dribbbles` 元素是所有项目的容器,每个 `dribbble` 元素代表页面上展示的设计之一。最后一个项目, `advert` ,是我们想在第二行显示的文本内容。

现在,让我们为它创建一个网格
<pre>.dribbbles {
display: grid;
/* 让我们假设默认设计有一个固定宽度 */
width: 880px;
grid-template-columns: repeat(4, 220px);
grid-template-areas
". . . ."
"advert advert advert advert";
justify-items: center;
}
.advert {
grid-area: advert;
}</pre>

在这里,我们定义了网格中的 4 列。我们没有谈论行,因此浏览器将根据需要自动创建行。
但是,我们确实使用 `grid-template-areas` 属性定义了几行,这是因为我们需要将广告文本内容放在第二行。所以我们只是说我们想要一个第一行,其中包含空单元格,这些单元格将随着网格项目的出现而自动填充,然后是一个第二行,它定义了一个 `advert` 区域,跨越所有 4 列。

最后,那里的第二条规则使用 `grid-area` 属性将具有类 `advert` 的网格项目放置在 `advert` 区域中。

鉴于我们之前创建的标记,这应该最终看起来像这样

很简单,对吧?当然,这需要一些样式代码才能使项目看起来漂亮,但布局 CSS 代码非常短,并且不需要额外的标记。

现在,我们也可以使它具有响应性。假设我们想要在较小的屏幕上跳转到 3 列布局
<pre>@media (max-width: 880px) and (min-width: 660px) {
.dribbbles {
width: 660px;
grid-template-columns: repeat(3, 220px);
grid-template-areas
". . ."
"advert advert advert";
}
}</pre>

等等,适用于我们想要支持的所有屏幕尺寸。因此,只需添加几个更短的媒体查询,我们就可以得到类似于以下内容的结果

像 Dribbble 这样的网站还没有使用 CSS 网格布局,但有一些众所周知的 CSS 库提供网格系统。

CSS 中网格系统的良好示例是 Bootstrap CSS 库 ,但还有其他一些,如 PureCSS960 Grid SystemResponsive Grid SystemMaterialize 等等。

不幸的是,这些网格系统必须使用精心调整大小和浮动的 DIV 的排列,并且需要额外的 DIV 用于列和行,以及用于清除浮动的伪元素。它并没有那么复杂——毕竟,这些库所依赖的技巧已经为人所知多年了——但它们仍然只是技巧,现在是时候让我们获得一个合适的内置解决方案,并摆脱它们了。

不要误会我的意思,这些库在各种方面都很棒,最重要的是,它们帮助我们明确了我们需要一个新的 CSS 功能来在 Web 布局中定义网格。因此,最终,它们是使 CSS 网格布局规范成为可能的。

结语和参考资料

正如我们所看到的,CSS 网格是一个非常好的工具,用于定义具有良好特性的布局,例如创作简单性、可维护性、内容和布局的分离——这些特性与响应式设计相得益彰。
但我们只是触及了 CSS 网格所能做到的皮毛。各种属性和语法可能性允许做很多非常酷的事情,我希望这篇文章能让你想自己深入挖掘。

如果你想了解更多,这里有一些有趣的参考资料

关于 Patrick Brosset

Patrick 管理着 Mozilla 的 DevTools 工程团队

更多 Patrick Brosset 的文章…


10 条评论

  1. Dylan Barrell

    当您说“其中一个方面是 HTML 标记中内容的顺序不再重要”时,您完全忽略了 HTML 内容的顺序始终很重要这一事实!

    它对搜索引擎很重要,更重要的是,它对像屏幕阅读器这样的工具的用户很重要。如果内容的顺序在视觉上很重要,以至于您觉得有必要更改顺序,那么您必须始终考虑这种更改对盲人或弱视用户(以及其他残疾用户)的影响。

    2015 年 9 月 9 日 09:20

    1. pbrosset@mozilla.com

      正确。我在文章中应该更清楚一些。我已经更新了那部分。谢谢。

      2015 年 9 月 9 日 14:27

  2. fantasai

    “其中一个方面是 HTML 标记中内容的顺序不再重要。”

    这不是真的。它的意思是视觉顺序可以自由地从内容顺序中更改。但源顺序很重要:它是用于语音和标签导航以及页面与其他非视觉交互的顺序。因此,HTML 标记中内容的顺序仍然非常重要!但借助 Grid 和 Flexbox,您可以优化标记以实现可访问性,而不会影响您操作视觉结果的能力。而 *这* 就是这项新功能的重要性所在。

    不幸的是,这一点在 grid 和 flex 教程中往往会被忽略,所以您是否介意更新您的教程以指出这一点?^_^ 谢谢~
    ~fantasai

    2015 年 9 月 9 日 10:03

    1. pbrosset@mozilla.com

      感谢您指出这一点。我已经更改了文章中反映您所说内容的部分(并且我还对您的话进行了改写 :) 希望这没关系)

      2015 年 9 月 9 日 14:26

  3. Greg Whitworth

    您说“HTML 的顺序并不重要”,这并不正确,因为存在潜在的可访问性问题。您需要确保 HTML 的结构是有意义的。如果您使用任何网格(或者为此目的的 flex 属性)来更改视觉顺序,并且它改变了所呈现内容的含义,则应通过更改 DOM 来完成此操作,而不是使用这些属性来确保您的网站对所有用户(包括使用可访问性 (a11y) 工具的人员)的行为相同。

    例如,假设您有一个简单的列 flex,它显示了一个食谱

    1. 取出鸡蛋
    2. 打碎鸡蛋
    3. 将它们放入锅中

    然后您重新排序它们,以有效地获得

    3. 将它们放入锅中
    2. 打碎鸡蛋
    1. 取出鸡蛋

    这是一个有意义的更改,使用 a11y 工具的人将无法获得,因为它只是一个视觉更改。您应该在 DOM 中重新排序它们,以便使用屏幕阅读器进行标签操作的人员获得相同的有意义的结果。

    虽然我理解大多数重新排序用例对意义没有影响,但我们希望确保 Web 开发人员在使用这些视觉更改属性时考虑这一步骤。

    谢谢。

    2015 年 9 月 9 日 10:17

    1. pbrosset@mozilla.com

      谢谢 Greg。这是一个很好的观点,我在文章中遗漏了。我已经纠正了那部分。

      2015 年 9 月 9 日 14:25

  4. Ivan

    不错。

    我喜欢使用的方法。
    对于制作一个新的……“新标签”页面可能真的很有用?:)

    2015 年 9 月 9 日 13:18

  5. Thiago F

    您指的是什么“HTML 的顺序并不重要”!!!

    只是在开玩笑。=P
    它甚至都不再存在了。=)
    对不起,今天早上我有点慢。
    (可访问性规则!)

    第一次(真正地)接触 CSS Grid。我相信它是一个非常好的介绍。感觉我已经了解了!非常感谢。很棒的阅读。

    2015 年 9 月 10 日 07:47

  6. Adam

    除了 flexbox 已经得到了很好的支持之外,grid 和 flexbox 之间有什么区别?

    2015 年 9 月 11 日 15:19

    1. Patrick Brosset

      来自 http://hugogiraudel.com/2013/04/04/css-grid-layout/

      我认为 Grid 将非常适合使用高级元素组织布局结构,而 Flexbox 将最适合某些需要特定对齐、排序等模块,例如流体导航。

      另请参见:http://www.outsidethebracket.com/understanding-the-difference-between-css3-flexbox-grid-layout/

      2015 年 9 月 14 日 00:06

本文的评论已关闭。