【译】Grid 完整指南

@JChehe 2017-12-09 10:35:23发表于 JChehe/blog

原文链接:《A Complete Guide to Grid》

Grid 是 CSS 目前最强的布局系统。与 flexbox 这类一维系统不同,它是二维系统,能同时处理行与列。Grid 布局需要对父元素(作为 Grid 容器)及其子元素(作为 Grid 子项)应用 CSS。

简介

CSS Grid Layout(简称“Grid”) 是一个基于网格的二维布局系统,旨在完全改变基于网格设计的用户界面。CSS 一直被用于 Web 页面布局,却从未很好地解决这个问题。在一开始,我们使用 table,然后使用 float、position 和 inline-block。但所有这些方法本质都是 Hack,并落下了很多重要的功能(如垂直居中)。Flexbox 在一定程度上解决了这些问题,但其主要针对的是一维布局,对于复杂的二维布局则显得力不从心(当然 Flexbox 与 Grid 结合使用效果更佳)。Grid 是首个专门用于解决布局问题而创建的 CSS 模块。

有两个主要因素驱动我编写这篇教程。第一个是 Rachel Andrew 的书 《Get Ready for CSS Grid Layout》,该书清晰且彻底地介绍了 Grid,这也是本文的基础。我强烈推荐你购买阅读此书。另一个重要因素是 Chris Coyier 的 《A Complete Guide to Flexbox》,这是我查阅所有关于 Flexbox 知识的地方,它也帮助了很多开发者,在 Google 关键词 "Flebox" 当之无愧地排名第一 。你也将会发现本文与它有很多相似之处,这难道不应该向最佳榜样学习吗?

本文的意图是介绍最新规范中的 Grid 概念。因此,我不会覆盖过时的 IE 语法,并尽最大努力紧跟规范并更新本文。

基本知识与浏览器兼容性

首先需要通过 display: grid 定义 Grid 的容器元素,然后设置行与列的尺寸 grid-template-rowsgrid-template-columns,最后通过 grid-rowgrid-column 将子元素放置在 grid 中。与 Flexbox 类似,grid 子项的编写顺序与实际顺序并不是绝对一致的,通过 CSS 即可将子元素以任意顺序摆放,结合媒介查询语句调整子项顺序就变得轻而易举了。想象一下:对于整个页面的布局,只需数行 CSS 即可针对不同屏幕宽度进行重排。Grid 无疑是 CSS 有史以来最强大的模块之一。

截止 2017 年 3 月,大部分浏览器兼容了无浏览器内核标识前缀的原生 Grid 语法,其中包括 Chrome(含 Android)、Firefox、Safari(含 iOS)和 Opera。而 Internet Explorer 10 与 11 则以过时的语法实现老旧规范。Edge 已宣布支持,但目前还未真正实现。

浏览器的兼容性来自于 Caniuse,它提供了更详细的数据。数字代表浏览器从该版本起支持该特性。

Desktop

Chrome: 57 Opera: 44 Firefox: 52 IE: 11* Edge: 16 Safari: 10.1

Mobile/Tablet

iOS Safari: 10.3 Opera Mobile: No Opera Mini: No Android: 62 Android: 62 Android Firefox: 57

除了 Microsoft,其余浏览器厂家都等到规范成熟后再实现标准规范。这无疑是一件好事,因为这意味着我们不必兼顾多种语法。

在生产环境中使用 Grid 只是时间问题,但是现在正是学习它的时候了。

重要术语

在深入学习 Grid 概念前,理解术语是关键的。由于这里涉及的术语在概念上较为相似,如果不先记住它们在 Grid 规范中的定义,则很容易彼此混淆。但不必因此而担心,因为它们并不多。

Grid 容器(Container)

应用 display: grid 的元素。它是所有 Grid 子项的直接父元素。下面案例中,container 是 Grid 容器。

<div class="container">
    <div class="item item-1"></div>
    <div class="item item-2"></div>
    <div class="item item-3"></div>
</div>

Grid 子项(Item)

Grid 容器的直系子元素。item 元素是 Grid 子项,但 sub-item 不是。

<div class="container">
    <div class="item"></div>
    <div class="item">
        <p class="sub-item">
    </div>
    <div class="item"></div>
</div>

Grid 线(Line)

构成 Grid 结构的分界线。它们要不垂直(“Grid 列”),要不水平(“Grid 行”),彼此交叉或平行。下图黄线就是 Grid 列。

Grid Line

Grid 轨道(Track)

相邻 Grid 线之间的空间。你可以认为它们是 Grid 的行或列。下图是第二条与第三条水平 Grid 线之间的 Grid 轨道。

Grid Track

Grid 单元格(Cell)

相邻行与相邻列同时围住的空间。这是 Grid 的一个“单元”。下图中第一二行和第二三列围住的空间就是 Grid 单元格。

Grid Cell

Grid 区域(Area)

由四条 Grid 线围住的空间。一个 Grid 区域可由任意个 Grid 单元格组成。下图中的 Grid Area 由第一三行与第一三列围成。

Grid 属性目录

父元素属性(Grid 容器)

display

将元素定义为 Grid 容器,即为其内容建立一个新的 Grid 格式上下文(grid formatting context)。

取值:

  • grid:生成块级(block-level) Grid
  • inline-grid:生成行内(inline-level) Grid
  • subgrid:Grid 容器本身就是一个 Grid 子项(即嵌套 Grid)时,你可以使用该属性值表示当前 Grid 的行列大小继承自其父元素,而无需重新指定。
.container {
    display: grid | inline-grid | subgrid;
}

注意:columnfloatclearvertical-align 对 Grid 容器无效。

grid-template-columnsgrid-template-rows

使用空格符分隔的值列表指定网格的行和列。这些值表示轨道的大小,即相邻 Grid line 之间的空间。

取值:

  • <track-size> :可以是长度值、百分比或 Grid 剩余空间的占比(使用 fr 单位)
  • <line-name>:可填写任意名字
.container {
  grid-template-columns: <track-size> ... | <line-name> <track-size> ...;
  grid-template-rows: <track-size> ... | <line-name> <track-size> ...;
}

案例:

当轨道值之间只有一个空白字符时,Grid 线会被自动分配数字类型的名字:

.container {
    grid-template-columns: 40px 50px auto 50px 40px;
    grid-template-row: 25% 100px auot;
}

numerical names

但你可以明确指定 Grid 线的名字。注意 Grid 线名字的括号语法:

.container {
    grid-template-columns: [first] 40px [line2] 50px [line3] auto [col4-start] 50px [five] 40px [end];
    grid-template-rows: [row1-start] 25% [row1-end] 100px [third-line] auto [last-line];
}

explicitly name the lines

需要注意的是:可以为 Grid 线指定多个名字。例如,下面的第二条 Grid 线就拥有两个名字:row1-endrow2-start

.container {
  grid-template-rows: [row1-start] 25% [row1-end row2-start] 25% [row2-end];
}

如果值有重复部分,可以使用 repeat() 让值变得精简:

.container {
  grid-template-columns: repeat(3, 20px [col-start]) 5%;
}

fr 单位能让我们根据 Grid 容器剩余空间的比例设置轨道大小。例如,设置每个 Grid 子项占 Grid 容器的 1/3。

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

剩余空间即减去非伸缩 Grid 子项后的空间。例如下面案例的总可利用剩余空间是减去 50px 后的:

.container {
    grid-template-columns: 1fr 50px 1fr 1fr;
}

grid-template-areas

通过引用 Grid 区域名字定义 Grid 模板,而 Grid 区域通过 grid-area 属性指定。多个 Grid 区域名字则代表内容跨越多个 Grid 单元格。而 . 则代表一个空 Grid 单元格。而语法本身则提供了可视化的 Grid 结构。

取值:

  • <grid-area-name>:通过 grid-area 属性指定的 Grid 区域名字。
  • .:指定空 Grid 单元格
  • none:无 Grid 区域被定义
.container {
    grid-template-areas: "<grid-area-name> | . | none | ..."
    "...";
}

案例:

.item-a {
    grid-area: header;
}
.item-b {
    grid-area: main;
}
.item-c {
    grid-area: sidebar;
}
.item-d {
    grid-area: footer;
}

.container {
    grid-template-columns: 50px 50px 50px 50px;
    grid-template-rows: auto;
    grid-template-areas: 
        "header header header header"
        "main main . sidebar"
        "footer footer footer footer";
}

这将会创建一个 3 行 x 4 列的 Grid。整个顶行由 header 区域组成。中间行由两个 main 区域、一个空单元格和一个 sidebar 区域组成。底行是整个 footer

grid-template-areas

声明中的每行需要拥有相同数量的单元格。

你可以使用任意数量无间距的 . 去声明一个空单元格。因为多个 . 之间无空格时则仍表示为一个单元格。

注意,该语法并没有命名 Grid 线,只是命名了区域。其实,当使用该语法时,区域各端的边界线都是自动命名的。如果 Grid 区域的名字为 foo,那么该区域的起始行和起始列将会被命名为 foo-start,相应地,其最后一行和最后一列将会被命名为 foo-end。这就意味着线可能拥有多个名字,例如上述案例中的最左侧线拥有了 header-startmain-startfooter-start 三个名字。

grid-template

grid-template-rowsgrid-template-columnsgrid-template-areas 的简写。

取值:

.container {
    grid-template: none | subgrid | <grid-template-rows> / <grid-template-columns>;
}

它还接受一个更复杂但非常便捷的语法来指定三个属性。例如:

.container {
      grid-template:
            [row1-start] "header header header" 25px [row1-end]
            [row2-start] "footer footer footer" 25px [row2-end]
            / auto 50px auto;
}

这等同于:

.container {
      grid-template-rows: [row1-start] 25px [row1-end row2-start] 25px [row2-end];
      grid-template-columns: auto 50px auto;
      grid-template-areas: 
            "header header header" 
            "footer footer footer";
}

grid-template 不会重置隐式 Grid 属性(grid-auto-columnsgrid-auto-rowsgrid-auto-flow),因为这可能满足你大多数情况的需要,但还是建议你使用 grid 属性,而不是 grid-template

grid-column-gapgrid-row-gap

指定 Grid 线的粗细。你可以把它想象成在行/列之间的间隙宽度。

取值:

  • <line-size>:长度值。
.container {
    grid-column-gap: <line-size>;
    grid-row-gap: <line-size>;
}

案例:

.container {
    grid-template-columns: 100px 50px 100px;
    grid-template-rows: 80px auto 80px;
    grid-column-gap: 10px;
    grid-row-gap: 15px;
}

grid-column-gap 和 grid-row-gap

间隙只存在于行/列之间,而不存在于外层边界。

grid-gap

grid-row-gapgrid-column-gap 的简写。

取值:

  • <grid-row-gap> <grid-column-gap>:长度值
.container {
    grid-gap: <grid-row-gap> <grid-column-gap>
}

案例:

.container {
    grid-template-columns: 100px 50px 100px;
    grid-template-rows: 80px auto 80px; 
    grid-gap: 10px 15px;
}

若未指定 grid-column-gap,则其值与 grid-row-gap 相同。

justify-items

定义 Grid 子项内容在行轴的对齐方式(相应地,align-items 定义了在列轴上的对齐方式)。对 Grid 容器内的所有 Grid 子项有效。

取值:

  • start:Grid 子项对齐于 Grid 区域左端
  • end:Grid 子项对齐于 Grid 区域右端
  • center:Grid 子项水平居中对齐于 Grid 区域
  • stretch:填满整个 Grid 区域宽度(默认值)

案例:

.container {
    justify-items: start;
}

justify-items: start

.container {
    justify-items: end;
}

justify-items: end;

.container {
    justify-items: center;
}

justify-items: center;

.container {
    justify-items: stretch;
}

justify-items: stretch;

可通过 justify-self 属性为某个 Grid 子项单独设置同样的行为。

align-items

定义 Grid 子项内容在列轴的对齐方式(相应地,justify-items 定义了在行轴上的对齐方式)。对 Grid 容器内的所有 Grid 子项有效。

取值:

  • start:Grid 子项对齐于 Grid 区域顶端
  • end:Grid 子项对齐于 Grid 区域底部
  • center:Grid 子项垂直居中对齐于 Grid 区域
  • stretch:填满整个 Grid 区域高度(默认值)
.container {
    align-items: start | end | center | stretch;
}

案例:

.container {
    align-items: start;
}

align-items: start;

.container {
    align-items: end;
}

align-items: end;

.container {
    align-items: center;
}

align-items: center;

.container {
    align-items: stretch;
}

align-items: stretch;

可通过 align-self 属性为某个 Grid 子项单独设置同样的行为。

justify-content

当 Grid 子项都以非可伸缩单位(如 px)指定尺寸时,Grid 的实际尺寸可能会小于其 Grid 容器的尺寸。在这种情况下,你可以设置 Grid 在 Grid 容器内的对齐方式。该属性是指定沿行轴上的对齐方式(相应地,align-content 是沿列轴上的对齐方式)。

取值:

  • start:Grid 对齐于 Grid 容器左端
  • end:Grid 对齐于 Grid 容器右端
  • center:Grid 水平居中对齐于 Grid 容器
  • stretch:调整 Grid 子项宽度以填满 Grid 容器的宽度。
  • space-around:在每个 Grid 子项间放置一个相同大小的间隙,而左右两端 Grid 子项的外侧放置一半大小的间隙。
  • space-between:在每个 Grid 子项间放置一个相同大小的间隙,而左右两端 Grid 子项的外侧无间隙。
  • space-evenly:在每个 Grid 子项间放置一个相同大小的间隙,而且左右两端 Grid 子项的外侧间隙大小也相同。
.container {
    justify-content: start | end | center | stretch | space-around | space-between | space-evenly;	
}

案例:

.container {
    justify-content: start;
}

justify-content: start;

.container {
    justify-content: end;	
}

justify-content: end;

.container {
    justify-content: center;	
}

justify-content: center;

.container {
    justify-content: stretch;	
}

justify-content: stretch;

.container {
    justify-content: space-around;	
}

justify-content: space-around;

.container {
    justify-content: space-between;	
}

justify-content: space-between;

.container {
    justify-content: space-evenly;	
}

justify-content: space-evenly;

align-content

当 Grid 子项都以非可伸缩单位(如 px)指定尺寸时,Grid 的实际尺寸可能会小于其 Grid 容器的尺寸。在这种情况下,你可以设置 Grid 在 Grid 容器内的对齐方式。该属性是指定沿列轴上的对齐方式(相应地,justify-content 是沿列轴上的对齐方式)。

取值:

  • start:Grid 对齐于 Grid 容器顶端
  • end:Grid 对齐于 Grid 容器顶部
  • center:Grid 垂直居中对齐于 Grid 容器
  • stretch:调整 Grid 子项高度以填满 Grid 容器的高度。
  • space-around:在每个 Grid 子项间放置一个相同大小的间隙,而上下两端 Grid 子项的外侧放置一半大小的间隙。
  • space-between:在每个 Grid 子项间放置一个相同大小的间隙,而上下两端 Grid 子项的外侧无间隙。
  • space-evenly:在每个 Grid 子项间放置一个相同大小的间隙,而且上下两端 Grid 子项的外侧间隙大小也相同。
.container {
    align-content: start | end | center | stretch | space-around | space-between | space-evenly;	
}

案例:

.container {
    align-content: start;	
}

align-content: start;

.container {
    align-content: end;	
}

align-content: end;

.container {
    align-content: center;	
}

align-content: center;

.container {
    align-content: stretch;	
}

align-content: stretch;

.container {
    align-content: space-around;	
}

align-content: space-around;

.container {
    align-content: space-between;	
}

align-content: space-between;

.container {
    align-content: space-evenly;	
}

align-content: space-evenly;

grid-auto-columnsgrid-auto-rows

指定自动生成的 Grid 轨道大小(又称“隐式 Grid 轨道”)。隐式 Grid 轨道会在指定 Grid 子项行列位置超出定义的 Grid 范围时(通过 grid-template-rowsgrid-template-columns)创建。

取值:

  • <track-size>:长度值、百分比或 Grid 剩余空间占比(使用 fr 单位)。
.container {
    grid-auto-columns: <track-size> ...;
    grid-auto-rows: <track-size> ...;
}

为了说明隐式 Grid 轨道是如何创建的,请思考一下:

.container {
    grid-template-columns: 60px 60px;
    grid-template-rows: 90px 90px
}

grid-template-columns 和 grid-template-rows
创建了一个 2x2 的 Grid

但现在假设你使用 grid-rowgrid-column 指定 Grid 子项:

.item-a {
    grid-column: 1 / 2;
    grid-row: 2 / 3;
}
.item-b {
    grid-column: 5 / 6;
    grid-row: 2 / 3;
}

grid-column: 5 / 6;

我们告诉 .item-b 在第五列开始,并在第六列结束,但我们并未定义第五和第六列。因为我们引用的列并不存在,所以会以宽度为 0 的隐式轨道填充缺口。我们可以使用 grid-auto-rowsgrid-auto-columns 指定这些隐式轨道的宽度:

.container {
    grid-auto-columns: 60px;
}

grid-auto-columns: 60px;

grid-auto-flow

当有 Grid 子项未在 Grid 中明确设置位置时,自动放置算法(auto-placement-algorithm)会自动将其放置在相应位置。所以该属性是控制自动放置算法的工作方式。

取值:

  • row:告诉自动放置算法依次填充每行,必要时会添加新行
  • column:告诉自动放置算法依次填充每列,必要时会添加每列
  • dense:告诉自动放置算法尽快填满 Grid,即大区域优先填充。
.container {
    grid-auto-flow: row | column | row dense | column dense
}

需要注意的是,dense 可能会导致子项乱序。

案例:

考虑以下 HTML:

<section class="container">
    <div class="item-a">item-a</div>
    <div class="item-b">item-b</div>
    <div class="item-c">item-c</div>
    <div class="item-d">item-d</div>
    <div class="item-e">item-e</div>
</section>

定义一个二行五列的 Grid,并设置 grid-auto-flowrow(默认值):

.container {
    display: grid;
    grid-template-columns: 60px 60px 60px 60px 60px;
    grid-template-rows: 30px 30px;
    grid-auto-flow: row;
}

只指定两个 Grid 子项:

.item-a {
    grid-column: 1;
    grid-row: 1 / 3;
}
.item-e {
    grid-column: 5;
    grid-row: 1 / 3;
}

由于 grid-auto-flow 设置为 row,Grid 看起来如下图。未被显示定位的三个子项(item-bitem-citem-d)会横向流动在空余行空间:

grid-auto-flow: row;

如果将 grid-auto-flow 设置为 columnitem-bitem-citem-d 会沿空余列空间向下流动:

.container {
    display: grid;
    grid-template-columns: 60px 60px 60px 60px 60px;
    grid-template-rows: 30px 30px;
    grid-auto-flow: column;
}

grid-auto-flow: column;

grid

grid-template-rowsgrid-template-columnsgrid-template-areasgrid-auto-rowsgrid-auto-columnsgrid-auto-flow 的简写。它会将 grid-row-gapgrid-column-gap 设为初始值,尽管不能通过该属性明确设置它们。

取值:

.container {
    grid: none | <grid-template-rows> / <grid-template-columns> | <grid-auto-flow> [<grid-auto-rows> [/ <grid-auto-columns>]];
}

案例:

下面两段代码是等价的:

.container {
    grid: 200px auto / 1fr auto 1fr;
}
.container {
    grid-template-rows: 200px auto;
    grid-template-columns: 1fr auto 1fr;
    grid-template-areas: none;
}

下面两段代码也是等价的:

.container {
    grid: column 1fr / auto;
}
.container {
    grid-auto-flow: column;
    grid-auto-rows: 1fr;
    grid-auto-columns: auto;
}

它还可以接受一次性设置所有子属性的语法,复杂却便捷。指定 grid-template-areasgrid-template-rowsgrid-template-columns 值时,其余子属性均被设置相应的初始值。你需要做的是:行内指定 Grid 线名字、轨道大小和各自 Grid 区域。最简单的说明案例:

.container {
    grid: [row1-start] "header header header" 1fr [row1-end]
        [row2-start] "footer footer footer" 25px [row2-end]
        / auto 50px auto;
}

等同于:

.container {
    grid-template-areas: 
        "header header header"
        "footer footer footer";
    grid-template-rows: [row1-start] 1fr [row1-end row2-start] 25px [row2-end];
    grid-template-columns: auto 50px auto;    
}

子元素属性(Grid 子项)

grid-column-startgrid-column-endgrid-row-startgrid-row-end

通过引用特定的 Grid 线来确定 Grid 子项在 Grid 内的位置。grid-column-start / grid-row-start 指定了 Grid 子项的起始线,grid-column-end / grid-row-end 指定了 Grid 子项的结束线。

取值:

  • <line>:数字引用已编号的 Grid 线,名字引用已命名的 Grid 线
  • span <number>:该子项会跨越 <number> 个 Grid 轨道
  • span <name>:该子项会跨越至 <name> Grid 线
  • auto:表示自动放置,自动跨越或默认跨度为 1
.item {
    grid-column-start: <number> | <name> | span <number> | span <name> | auto
    grid-column-end: <number> | <name> | span <number> | span <name> | auto
    grid-row-start: <number> | <name> | span <number> | span <name> | auto
    grid-row-end: <number> | <name> | span <number> | span <name> | auto
}

案例:

.item-a {
    grid-column-start: 2;
    grid-column-end: five;
    grid-row-start: row1-start;
    grid-row-end: 3;
}

grid-column-start、grid-column-end、grid-row-start、grid-row-end

.item-b {
    grid-column-start: 1;
    grid-column-end: span col4-start;
    grid-row-start: 2
    grid-row-end: span 2
}

span

如果未声明 grid-column-end / grid-row-end,Grid 子项会默认跨越一个轨道。

Grid 子项会相互覆盖,这时可通过 z-index 控制它们的层叠顺序。

grid-columngrid-row

分别是 grid-column-start + grid-column-endgrid-row-start + grid-row-end 的简写。

取值:

  • <start-line> / <end-lune>:各自取值与非简写时一致,也就包括 span
.item {
    grid-column: <start-line> / <end-line> | <start-line> / span <value>;
    grid-row: <start-line> / <end-line> | <start-line> / span <value>;
}

案例:

.item-c {
    grid-column: 3 / span 2;
    grid-row: third-line / 4;
}

<start-line> / <end-line>

若未声明结束线(end line),则 Grid 子项默认跨度为 1。

grid-area

通过赋予 Grid 子项名字,引用 grid-template-areas 属性创建的模板。另外,该属性可用作 grid-row-start + grid-column-start + grid-row-end + grid-column-end 的简写。

取值:

  • <name>:你选择的名字
  • <grid-start> / <column-start> / <row-end> / <column-end>:代表线的数值或名字
.item {
    grid-area: <name> | <row-start> / <column-start> / <row-end> / <column-end>;
}

案例:

为 Grid 子项分配名字:

.item-d {
    grid-area: header
}

作为 grid-row-start + grid-column-start + grid-row-end + grid-column-end 的简写:

.item-d {
    grid-area: 1 / col4-start / last-line / 6
}

grid-area shorthand

justify-self

设置 Grid 子项沿行轴的对齐方式(相应地,align-self 沿列轴)。该属性值仅对当前子项有效。

取值:

  • start:Grid 子项对齐于 Grid 区域左端
  • end:Grid 子项对齐于 Grid 区域右端
  • center:Grid 子项水平居中对齐于 Grid 区域
  • stretch:填满整个 Grid 区域宽度(默认值)
.item {
    justify-self: start | end | center | stretch;
}

案例:

.item-a {
    justify-self: start;
}

justify-self: start;

.item-a {
    justify-self: end;
}

justify-self: end;

.item-a {
    justify-self: center;
}

justify-self: center;

.item-a {
    justify-self: stretch;
}

justify-self: stretch;

要对 Grid 内所有子项设置沿行轴的对齐方式,可对 Grid 容器设置 justify-items 属性。

align-self

设置 Grid 子项沿列轴的对齐方式(相应地,align-self 沿行轴)。该属性值仅对当前子项有效。

取值:

  • start:Grid 子项对齐于 Grid 区域顶端
  • end:Grid 子项对齐于 Grid 区域底部
  • center:Grid 子项垂直居中对齐于 Grid 区域
  • stretch:填满整个 Grid 区域高度(默认值)
.item {
    align-self: start | end | center | stretch;
}

案例:

.item-a {
    align-self: start;
}

align-self: start;

.item-a {
    align-self: end;
}

align-self: end;

.item-a {
    align-self: center;
}

align-self: center;

.item-a {
  align-self: stretch;
}

align-self: stretch;

要对 Grid 内所有子项设置沿列轴的对齐方式,可对 Grid 容器设置 align-items 属性。