最强大的 CSS 布局方案——网格布局

300 阅读11分钟

最强大的 CSS 布局方案——网格布局

基本概念

  1. 网格(Grid)

    网格是一组相交的水平线和垂直线,它定义了网格的行和列

  2. 网格线(Grid lines)

    使用 Grid 布局在显式网格中定义轨道的同时会创建网格线

  3. 网格轨道(Grid tracks)

    网格轨道是两条网格线之间的空间

  4. 网格单元格(Grid cell)

    在 Grid 布局中,网格单元格是 CSS 网格中的最小单元,它是四条网格线之间的空间

  5. 网格区域(Grid areas)

    网格区域是网格中由一个或者多个网格单元格组成的一个矩形区域

  6. 网格间隙(Gutters)

    网格间隙是网格轨道之间的间距

  7. 网格轴(Grid Axis)

    任何网格中都有两个轴,横轴(行轴)和纵轴(列轴)

  8. 网格行(Grid row)

    网格行是 Grid 布局中的水平轨道,即两条水平网格线之间的空间

  9. 网格列(Grid column)

    网格列是 Grid 布局中的垂直轨道,即两条垂直网格线之间的空间

  10. 其他概念

    • 网格容器与网格项目

      通过设置 CSS 属性display:grid;或者display:inline-grid;可以创建一个 CSS 网格容器,网格容器中的直接子元素称为网格项目

    • 显式网格与隐式网格

      可以使用grid-template-rows/columns属性定义网格的行和列。使用这些属性定义的网格被称为显式网格。如果在网格容器中放置了超过网格数量的 grid items,那么网格算法将会创建额外的 row、column、tracks 来包含这些内容,此时额外的部分被称为隐式网格

利用网格进行布局

创建并划分网格容器

.grid-container {
    /* 创建网格容器 */
    display: grid;
    /* display: inline-grid; */
    /* 在容器中显式地划分行与列,生成指定数量的单元格来放置项目 */
    grid-template-rows: 100px 100px;
    grid-template-columns: 100px 100px 100px;
    /* 上面两行代码也可以简写成如下形式 */
    /* grid-template: 100px 100px / 100px 100px 100px; */
    /* 设置项目在容器中的排列/填充方式,row 表示行优先,column 表示列优先,默认值为 row */
    grid-auto-flow: row;
    /* grid-auto-flow: column; */
    /* 设置隐式网格的行高和列宽 */
    grid-auto-rows: 150px;
    /* grid-auto-columns: 200px; */
}

备注:

  1. grid 与 inline-grid 的区别:grid 容器为 block 特性,宽度默认为 100%,不能和图片、文字等一行显示。inline-grid 容器为 inline 特性,可以和图片、文字等一行显示
  2. 网格项目默认为块盒,即 display 属性值为 block
  3. grid-auto-flow 的值为 row 时,grid-auto-rows 属性起作用;grid-auto-flow 的值为 column 时,grid-auto-columns 属性起作用
  4. 和弹性盒子布局中的 flex-direction 属性不同,grid-auto-flow 的值不会对网格轴的方向产生影响

设置单元格的数量和尺寸

.grid-container {
    /* 使用固定值 */
    grid-template-rows/columns: 40px 4em 40px;
    /* 使用百分比 */
    grid-template-rows/columns: 20% 100px 80%;
    /* 按比例划分 */
    grid-template-rows/columns: 1fr 2fr 1fr;
    grid-template-rows/columns: 200px 1fr;
    /* 使用 max-content 与 min-content 关键字 */
    grid-template-rows/columns: max-content min-content 1fr;
    /* auto */
    grid-template-rows/columns: 200px auto;
    /* 使用 minmax() 函数 */
    grid-template-columns: 100px minmax(100px, auto) 100px;
    /* 使用 repeat() 函数 */
    grid-template-rows/columns: 20px repeat(6, 1fr) 20px;
    grid-template-rows/columns: repeat(5, 1fr 2fr);
    grid-template-rows/columns: repeat(auto-fit, minmax(100px, 1fr));
    grid-template-rows/columns: repeat(auto-fill, minmax(100px, 1fr));
    /* 使用 fit-content() 函数 */
    grid-template-rows/columns: fit-content(200px) 1fr 1fr;
    grid-template-rows/columns: fit-content(40%) auto;
}

备注:

  1. fr 是网格布局所引入的一个新的长度单位,表示网格容器中可用空间的一等份
  2. CSS 中存在两种尺寸:内部尺寸和外部尺寸。为元素的 width、height 设置的固定属性值就是外部尺寸,而内部尺寸,则是由元素所包含的内容决定的。min-content 和 max-content 就是一种内部尺寸。min-content 表示能容纳内容的最小宽度。在英文句子中,通常是最长单词的长度;而在中文句子中,通常是一个字的长度。与 min-content 相对应的是 max-content,它表示内容所能达到的最大宽度
  3. 将网格轨道的尺寸设置为 auto 时,浏览器会根据以下规则来自动计算其大小:
    1. 首先,浏览器会确定所有非 auto 轨道的大小。非 auto 的网格轨道可以通过指定固定值(如像素)或使用其它属性(如百分比、fr 等)来定义其大小
    2. 接下来,如果没有任何一个非 auto 轨道,则所有网格轨道都被视为 auto,并且它们的大小将根据网格项目的大小和位置自动计算
    3. 如果有多个 auto 轨道,则其中一个或多个轨道的大小会被自动设置为其内容所需的最小空间,而其余的 auto 轨道则会平均分配容器中剩余的可用空间。这时候就需要考虑到网格项目的大小和位置了
    4. 如果只有一个 auto 轨道,则其大小将根据网格项目的大小和位置来自适应调整,以填充网格容器中的所有可用空间
  4. auto-fill 与 auto-fit 的区别:区别只在于单行时对于剩余空间的处理。auto-fill 是有足够空间就创建空白列,剩余不到一列时才均分剩余空间。而 auto-fit 始终是均分剩余空间
  5. fit-content() 函数表示将元素宽度收缩到内容宽度。如果内容的宽度小于 fit-content 中设置的宽度,那么实际宽度就是内容的宽度;如果内容的宽度大于 fit-content 中设置的宽度,那么实际宽度就是 fit-content 中设置的宽度

使用默认网格线定位元素

创建一个网格容器,并显式的划分行与列:

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

使用默认的网格线编号来定位元素:

.grid-item {
    grid-column-start: 1;
    grid-column-end: 2;
    grid-row-start: 1;
    grid-row-end: 4;
}

使用grid-columngrid-row对上述代码进行简写:

.grid-item {
    grid-column: 1 / 2;
    grid-row: 1 / 4;
}

由于元素默认延伸一个轨道。所以,当元素只延伸一个轨道时,可以省略grid-column-endgrid-row-end。由此,上方代码可以进一步简写为:

.grid-item {
    /* grid-column-start: 1;
    grid-row-start: 1;
    grid-row-end: 4; */
    grid-column: 1;
    grid-row: 1 / 4;
}

对于行或列的编号,也可以反方向计数。-1 表示横向或纵向的最后一条线,-2 表示横向或纵向的倒数第二条线,以此类推。所以,上方的代码也等价于:

.grid-item {
    /* grid-column-start: -3;
    grid-column-end: -4;
    grid-row-start: -1;
    grid-row-end: -4; */
    grid-column: -3 / -4;
    grid-row: -1 / -4;
}

除了“起始线+结束线”的定位方式,还可以使用“起始线+跨越轨道数量”的定位方式:

.grid-item {
    /* grid-column-start: 1;
    grid-row-start: 1;
    grid-row-end: span 3; */
    grid-column: 1;
    grid-row: 1 / span 3;
}

使用命名网格线定位元素

创建一个网格容器,并对网格线进行命名:

.grid-container {
    display: grid;
    grid-template-columns: [c1-start] 100px [c1-end c2-start] 100px [c2-end c3-start] 100px [c3-end];
    grid-template-rows: [r1-start] 100px [r1-end r2-start] 100px [r2-end r3-start] 100px [r3-end];
}

使用命令网格线来定位元素:

.grid-item {
    /* grid-column-start: c1-start;
    grid-row-start: r1-start;
    grid-row-end: r3-end; */
    grid-column: c1-start;
    grid-row: r1-start / r3-end;
}

使用 repeat() 函数设置轨道尺寸时,命名的网格线会自动添加索引。将上述命名网格线的代码修改为如下形式:

.grid-container {
    display: grid;
    grid-template-columns: repeat(3, [col-start] 100px [col-end]);
    grid-template-rows: repeat(3, [row-start] 100px [row-end]);
}

修改网格项目的 CSS 代码为:

.grid-item {
    /* grid-column-start: col-start 1;
    grid-column-end: col-end 1;
    grid-row-start: row-start 1;
    grid-row-end: row-end 3; */
    grid-column: col-start 1 / col-end 1;
    grid-row: row-start 1 / row-end 3;
}

可以发现,两种方式达到的效果是相同的

使用网格区域定位元素

创建一个网格容器,并显式的划分行与列:

.grid-container {
    display: grid;
    grid-template-columns: repeat(4, 1fr);
    grid-template-rows: repeat(4, 1fr);
}

使用网格区域定位元素:

.grid-item {
    /* 各个值分别代表起始行、起始列、结束行、结束列的编号 */
    grid-area: 1 / 1 / 4 / 2;
}

在创建网格容器时,可以使用grid-template-areas来命名网格区域:

.grid-container {
    display: grid;
    grid-template-columns: 80px 1fr 80px;
    grid-template-rows: 40px 1fr 40px;
    /* 命名网格区域,注意:名称相同的相邻网格区域会合并成一个更大的网格区域 */
    grid-template-areas:
        "header header header"
        "left main right"
        "footer footer footer";
    /* 上述划分网格行/列和命名网格区域的代码可以简写为如下形式: */
    /* grid-template:
        "header header header" 40px
        "left main right" 1fr
        "footer footer footer" 40px / 80px 1fr 80px; */
}

基于上述代码,添加如下代码就可以实现一个简单的网页布局:

.grid-item-header {
    grid-area: header;
}
.grid-item-left {
    grid-area: left;
}
.grid-item-main {
    grid-area: main;
}
.grid-item-right {
    grid-area: right;
}
.grid-item-footer {
    grid-area: footer;
}

事实上,对网格区域命名后,网格区域的四条边都会有一个默认名称。区域起始行/列的名称为<areaName>-start,区域结束行/列的名称为<areaName>-end。所以,将上述代码修改成如下形式也可以达到相同的效果:

.grid-item-header {
    grid-area: header-start / header-start / header-end / header-end;
}
.grid-item-left {
    grid-area: left-start / left-start / left-end / left-end;
}
.grid-item-main {
    grid-area: main-start / main-start / main-end / main-end;
}
.grid-item-right {
    grid-area: right-start / right-start / right-end / right-end;
}
.grid-item-footer {
    grid-area: footer-start / footer-start / footer-end / footer-end;
}

在命名网格区域时,可以使用占位符(.)来创建空白的网格单元格。这些网格单元格会占据网格容器中的空间,但不会显示任何内容。类似于 HTML 中的空白字符。以下是网格区域占位符的使用示例:

.grid-container {
    grid-template-areas:
        "header header header"
        ". . ."
        "footer footer footer";
}

设置对齐与间隙

  1. 设置所有单元格在容器中的对齐方式
    • 水平方向:justify-content: flex-start/center/flex-end/space-bewteen/space-around/space-evenly;
    • 垂直方向:align-content: flex-start/center/flex-end/space-bewteen/space-around/space-evenly;
    • 简写形式:place-content: <align-content> <justify-content>;,如果没有设置第二个值,则第二个值与第一个值相等
  2. 设置所有项目在单元格 / 网格区域中的对齐方式
    • 水平方向:justify-items: flex-start/center/flex-end/stretch;
    • 垂直方向:align-items: flex-start/center/flex-end/stretch;
    • 简写形式:place-items: <align-items> <justify-items>;,如果没有设置第二个值,则第二个值与第一个值相等
  3. 设置单个项目在单元格 / 网格区域中的对齐方式
    • 水平方向:justify-self: flex-start/center/flex-end/stretch;
    • 垂直方向:align-self: flex-start/center/flex-end/stretch;
    • 简写形式:place-self: <align-self> <justify-self>;,如果没有设置第二个值,则第二个值与第一个值相等
  4. 设置间隙
    • 设置网格行之间的间隙,例如:row-gap: 10px;
    • 设置网格列之间的间隙,例如:column-gap: 10px;
    • 简写形式:gap: <row-gap> <column-gap>;,如果未提供第二个值,则第一个值作为第二个值的默认值

特别注意:简写属性不仅能够一口气把多个属性简写成一行,还会把一些没有设置过的值、或不能在简写中设置的值重置成初始值。所以如果要用简写方式,一定要意识到它可能会把在别处已经设置过的值给重置

附录

auto-fill 与 auto-fit 的区别

考虑如下代码:

<div class="grid-container grid-container-fill">
    <span class="grid-item">1</span>
    <span class="grid-item">2</span>
    <span class="grid-item">3</span>
    <span class="grid-item">4</span>
    <span class="grid-item">5</span>
</div>
<div class="grid-container grid-container-fit">
    <span class="grid-item">1</span>
    <span class="grid-item">2</span>
    <span class="grid-item">3</span>
    <span class="grid-item">4</span>
    <span class="grid-item">5</span>
</div>
.grid-container {
    display: grid;
    margin: 50px 0;
}
.grid-item {
    background-color: deepPink;
    padding: 30px;
    color: #fff;
    border: 1px solid #fff;
}
.grid-container-fill {
    grid-template-columns: repeat(auto-fill, minmax(100px, 1fr));
}
.grid-container-fit {
    grid-template-columns: repeat(auto-fit, minmax(100px, 1fr));
}

可以发现:当容器的宽度不够容纳额外的列(gridContainerWidth < 600px)时,auto-fill 与 auto-fit 表现出来的效果一样。只有当容器的宽度足够容纳额外的列(gridContainerWidth >= 600px)时,这两者的区别才会体现出来:

  • 对于 auto-fill:它会暗中创建一些新列来填充当前行。即使创建出来的列没有任何内容,但实际上还是占据了行的空间
  • 对于 auto-fit:它会将行的剩余空间平均分配到所有项目中