zl程序教程

您现在的位置是:首页 >  前端

当前栏目

CSS – Grid

CSS Grid
2023-09-27 14:23:55 时间

前言

有一种布局方式叫 Layout Grid 网格布局.

Figma – Layout Grid 有介绍过.

在 RWD 概念篇 也有讲到过

要实现这种布局, 可以用 Flex 也可以用 Grid. Bootstrap 用的方法就是 Flex.

Grid 出生在 Flex 之后, 所以先掌握 Flex 在学 Grid 会比较轻松, 但它们 2 个是不一样的东西哦. 虽然有共同点但是整体还是区别蛮多的.

 

参考:

阮一峰 CSS Grid 网格布局教程

CSS Grid Layout Crash Course

 

理解

Flex 的用法是, 有一些 item 我们想把它排成 1 行或者 1 列.

Grid 的想法是我先做一个布局 (类似 table) 然后 item 在一个一个依据规则放进来.

所以它们的思路是不一样的. Flex 简单的很多. 它只考虑 1 行或者 1 列.

Grid 考虑的是整体, 所谓的 2 direction.

 

HTML 结构

<div class="container">
  <div class="item">item1</div>
  <div class="item">item2</div>
  <div class="item">item3</div>
  <div class="item">item4</div>
  <div class="item">item5</div>
  <div class="item">item6</div>
  <div class="item">item7</div>
  <div class="item">item8</div>
  <div class="item">item9</div>
</div>

和 Flex 完全一样. 也只有 first layer 才是 grid item.

 

Grid 结构: Row, Column, Cell, Line

黄色 item 1, 2, 3 叫 row 行

红色 item 1, 4, 7 叫 column 列

蓝色 2 条线叫 column line 1, column line 2

青色 item 9 叫 cell 

和 table 的叫法差不多.

 

grid-template-columns/rows

Grid 的第一步是先定义布局. 多少 column, row, 多大. 不需要去考虑 item 先.

display: grid;
grid-template-columns: 100px 100px 100px;

 这表示有 3 个 column, 每一个 100px width,  大概是这个画面

display: grid;
grid-template-columns: 100px 100px 100px;
grid-template-rows: 100px 100px;

grid-template-rows 表示 3 个 row, height 100px, 大概是这个画面

于是网格就建立起来了. 

item 会一个一个的被丢进去. 默认的顺序是 Z 字形

我们只定义了 2 rows 100px 所以第 3 行的 height 是 hug content (后面会讲到细节)

这就是 Grid 的 flow, 先布局, 然后 item 依据规则插入进去,

 

repeat(), col/row, dimension

repeat()

它就是一个方便而已. 第一次参数是数量, 第 2 参数是值

grid-template-columns: repeat(3, 100px);
grid-template-rows: repeat(2, 100px 100px);

auto-fill

grid-template-columns: repeat(auto-fill, 100px);

auto-fill 作为 repeat 的第一参数表示自动计算 column 数量, base on container width 和 item width. 尽可能的用完 container width

auto-fit

参考: 

grid里 auto-fill和auto-fit的区别

dimension

px 固定一个值

% 依据 container 取 percentage (它不管 gap 的哦)

fr 是比例 fraction

repeat(3, 1fr) 表示 3 个 columns 每一个 33.33% (前提是 item 内没有大于 33.33% width, 下面会讲到细节)

1fr 1fr 2fr 表示 25% 25% 50%

100px 1fr 1fr 表示 100px 50% 50% (扣除已知空间后剩余空间 50%)

auto 是伸缩, 它会先扣除已知空间,

比如 100px 100px auto, 那就先扣除 200px, 然后剩余的就是 auto

如果有多个 auto 的话, 它会先满足第一个 auto 的 item, 

尽可能给 max-content, 如果最后有剩余空间, 在另外分配. 需要注意的是, 这个分配是按内容比例而不是平均分的

下面是 3 个 auto 的例子. item2 的字比较多, 最终整个 item width 也比较多

minmax(100px, 1fr) 表示 auto 但是最小 100px 最大 1fr. (这常用于实现 Layout Grid)

min-content 如果 item 是 block 那么效果类似 hug content, inline 的话参考这篇, 类似 flex 的效果, 但是 grid 做不到 flex 那样 shrink 哦

max-content 如果 item 是 block 那么效果类似 hug content, inline 的话参考这篇,

fix-content(100px) 这个是方法哦, 最小是 min-content, 最大是 max-content, 如果参考没有超过范围就用参数 100px 

重要: repeat(3, 1fr) vs repeat(3, minmax(1fr))

例子说明:

<div class="container">
  <div class="box1">
    <p>
      Lorem ipsum dolor sit amet consectetur adipisicing elit. Ipsam, omnis!
    </p>
  </div>
  <div class="box2">
    <p>
      Lorem ipsum dolor sit amet consectetur adipisicing elit. Ipsam, omnis!
    </p>
  </div>
  <div class="box3">
    <p>
      Lorem ipsum dolor sit amet consectetur adipisicing elit. Ipsam, omnis!
    </p>
  </div>
</div>

container 里面 3 个 box

.container {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 1rem;
  width: 1000px;
}

效果

3 个 box 的 width 是一样的, 322.66px, 但是如果 box1 有一个固定的 width > 33.33% 假设 400px

.box1 {
  p {
    width: 100px;
  }
}

效果

box1 的 col 变成了 400px 被撑大了. 这就是所谓的扣了已知空间后按比例分配.

所以一般上做 Layout Grid 时, 设定 column 的写法时 repeat(3, minmax(0, 1fr)), 它的意思是最大是 1fr. 限制了 box 的上限.

grid-template-columns: repeat(3, minmax(0, 1fr));

效果

box1 的 p 已经 overflow 了. 证明 box1 的 width 只有 33.33%. 下面有 Layout Grid 的实现, 可以看出更多细节.

 

小总结

Grid 有 2 个步骤,

第一步是设定好布局, 多少 column, rows, 每个 cell 多大.

虽然 cell 的 width 有很多种 set 法, 但是它做不到 flex 那样可以 shrink item.

第二步是 item 规则插入.

到这里第一步需要的属性都介绍了. 去下一步 item 规则插入.

 

Item Size vs Cell Size

当 item 超出 cell 的时候会怎样? 答案是重叠.

item1,2 都是 200px, 但是 cell 只有 100px, 于是 item 1 超出去 cell 覆盖到了 item2 上面.

当 item 小于 cell 的时候呢? 空白咯.

注意: 如果 item 的 width 是 auto, 那么它会去填满 cell width 即使它是 display inline

 

grid-column/row

默认情况下 1 item 会被插入到 1 个 cell 里头.

可以通过几种方式改变这个效果.

grid-template-columns: repeat(3, 1fr);

3 个 columns, 1 item 1 cell 长这样.

grid-column

用 grid-column 修改它

&:nth-child(1) {
  grid-column: 3 span;
}

3 span 表示这个 item 要占据 3 column cell. 所以效果是

另一种表达方式是

grid-column: 1 / 4;

它表示 line 1 to line 4, 和 3 span 是等价的 (1 / -1 也是等价的, -1 是从后面算起第 1 条, 也就是 line 4)

混写可以哦, 1 / 2 span 从 line 1 开始 2 个 column cell.

grid-row

和 grid-column 同理, 只是方向不一样

grid-column: 1 / 2 span;
grid-row: 1 / 2 span;

效果

grid-area

可以通过 grid-area 混写 grid-column/row 哦

grid-area: 1 / 1 / 2 span / 2 span

它的顺序是 grid-row-start, grid-column-start, grid-row-end, grid-column-end

grid-template-area

除了 grid-template-column/row 还有一个叫 area 所见即所得布局, 它是这样的.

  grid-template-areas:
    "a a b c"
    "d e f ."
    "g h i .";

item 定义 grid-area: area-name

<div class="container">
  <div class="item" style="grid-area: a">item1</div>
  <div class="item" style="grid-area: b">item2</div>
  <div class="item" style="grid-area: c">item3</div>
  <div class="item" style="grid-area: d">item4</div>
  <div class="item" style="grid-area: e">item5</div>
  <div class="item" style="grid-area: f">item6</div>
  <div class="item" style="grid-area: g">item7</div>
  <div class="item" style="grid-area: h">item8</div>
  <div class="item" style="grid-area: i">item9</div>
</div>

第 1 排 a a b c, 所以 item 1 占据 2 column cell.

第 2 排结尾 d e f . 的点表示这个 cell 无用, 不放入 item

效果

 

grid-auto-flow

item 插入是有顺序的, 默认是 Z 字形, 先行后列.

grid-auto-flow 可以改变这个顺序. 

grid-auto-flow: row 先行后列, defualt, 这也是为什么只设置 display: grid 是没有效果的 (不像 flex 一设置就有效果了), 因为 item 先插入 row.

grid-auto-flow: colomn 先列后行

如果说 item 太大塞不进会怎样呢? 

item 3 是 span 2, 塞不进第 3 个 cell 于是被放到下面了, cell 3 则空白了.

通过 grid-auto-flow: row dense 可以让它智能排位, 尽可能不留空洞.

item 3,4 span 2 所以无法放进去. 结构 item 5, 6 反而放到了前面. 这就是 dense 的效果了.

 

grid-auto-columns/rows

假设我们只定义了 9 个 cell, 但是我有 12 个 item 会怎么样呢? 

grid 会 auto 创建 rows 来满足多出来的 item, 看例子: 

grid-template-columns: repeat(3, 1fr);
grid-template-rows: repeat(3, 1fr);
grid-auto-rows: 50px;

grid-auto-rows 的写法和 grid-template-rows 是一样的.

上面声明了 9 个 cell 和自动创建 row 的 height 是 100px, 效果:

如果写 50px 100px 表示第 2 个自动 row 是 100px 高. 第 3 个又是 50px 第 4 个又是 100px 以此类推.

如果没有定义 grid-auto-rows 等价于 auto (hug content)

 

gap (grid-gap)

cell 的间距. 如果 item 2 span 的话, item width = cell1 + gap + cell2 

gap: 10px 20px;

gap: row column

 

justify-items, align-items, justify-content, align-content

container

justify-items: center;
align-items: center;

item

justify-self: center;
align-self: center;

和 Flex 类似, container 只是一个方面批量 set 而已.

justify-items 和 justify-self 只有 Grid 才有, flex 是没有的哦. 看效果体会

justify 控制 row, align 控制 column, 它控制的是 item 在 cell 里面的 alignment.

此外, Grid 也有 justify-content, align-content

justify-content: center;
align-content: center;

它控制的是整个 grid cell 和 container 的 alignment 

place-items 是 justify-items 和 align-items 的 shorthand

place-items: center center;

place-items: align-items justify-items;

与 Flex 的对比

Flex (direction row)

justify-content: align horizontal all items with container.

align-content: align vertical all items with container. (only when wrap)

align-items: 批量操作

align-self: align vertical item with container

Grid

justify-content: align horizontal all cells with container. (Flex 有)

align-content: align vertical all cells with container. (Flex 有)

justify-items: 批量操作

align-items: 批量操作 (Flex 有)

place-items: shorthand

justify-self: align horizontal item width cell

align-self: align vertical item width cell (Flex 有)

place-self: shorthand

 

Grid Reverse

参考: StackOverflow – Reverse order of columns in CSS Grid Layout

flex-direction: row-reverse 可以让 items 变成逆序. Grid 没有这种功能.

Grid 唯一能做的就是在 item 加上 order, 或者用 grid-area 来自定义位置.

.container {
  display: grid;
  grid-template-columns: max-content auto;
  > :first-child {
    order: 2;
  }
}

 

当 auto-fit minmax 遇上 max-content

在 CSS 世界里, 很多时候只能其中 1 边 dynamic. 

比如, 当 aspect-ratio 遇上 Flex, Grid 也是有这样的局限.

minmax, max-content, min-cotent 这些用来 dynamic set width 的. 

auto-fit 是用来 dynamic set column count 的.

这 2 者不可以同时设置

grid-template-columns: repeat(auto-fit, minmax(max-content, 1fr));

stackoverflow – CSS Grid auto fit with max-content

遇到这种情况没有解决方法, 只能闪. 不然就 JS 呗.

 

Grid vs Flex

参考: 

Youtube – CSS Grid vs Flexbox

Youtube – Flexbox vs. CSS Grid — Which is Better?

Youtube – Flexbox or grid - How to decide?

Youtube – Flexbox vs. CSS Grid: Which Should You Use and When?

虽然有些效果 Grid, Flex 都可以实现, 但我们尽量看它们不同的地方, 这样才能各取所长.

我喜欢这 2 张对比图, 看它的线, Grid 十字对齐

再看看 Flex 的, 它的 vertical 线是对不上的. 所谓的 2 direction vs 1 direction

Grid 重叠

flex 做不到重叠, 重叠在一些情况下很不错用, 可以取代 position absolute.

比如这种

Grid + Flex

Grid 适合大布局, 整体那种, flex 适合小布局. 所以很多排版会用 Grid 里面加 Flex.

可以看这个.Using Flexbox + CSS Grid Together: Easy Gallery Layout 

Structured layout vs intrinsic sizing

Flex 擅长做这种, intrinsic sizing, 那种想 shrink 的

Grid 擅长做这种, 2 边都有比较固定的尺寸的.

当用 Flex 的时候, 往往不会去控制 width. 会让 item 跟自己的 width 走.

而 Grid 往往是让 item 跟 column 的 width 做. 这是 2 个比较明显的区别.

Flex 比 Grid 厉害的地方

这个结构的特色是最后一行是 2 column.

Flex 要实现这个结构需要用到 wrap + grow. RWD dynamic column

Grid 要实现这个结构需要在 item 声明 span. 它无法用 dynamic column 的方式去实现.

没有 dynamic column 意味着在同一个 breakpoint 下就不可能出现小的时候 1 column, 大一点的时候 2 column 了.

而 Flex 却可以做到. 所以 Grid 在处理 RWD 时, 通常是 1 个 breakpoint 1 个 column pattern. Flex 却可以多个 pattern.

 

one direction 但是 Grid 比较好用的情况

Layout Grid by Flex

Bootstrap 的 Layout Grid 是用 Flex 做的. 以前有提过: Layout Grid / Grid-View (更新: 04 June 2022: v5.1 以后不再使用 Flex 改而用 Grid 实现了)

它的实现手法比较麻烦, 比如想做一个 width: 600, 8:4 比例, gutter 16px 的布局, 

HTML

<div class="container">
  <div class="row">
    <div class="box1">box1</div>
    <div class="box2">box2</div>
  </div>
</div>

CSS

.container {
  // padding: 0 8px; /* Bootstrap 默认 container 是有 padding 的, 这次 demo 不拿来教具 */
  border: 1px solid black; /* just for 美观 */
  width: 600px; /* 限定宽度 */

  .row {
    margin: 0 -8px; /* 抵消掉 box margin 因为那个是用来做 gutter/gap 的 */

    display: flex;

    .box1,
    .box2 {
      margin: 0 8px; /* 用来做 gutter/gap */ (Bootstrap 是用 padding, 但有个缺点是 background-color 不好 set, 所以我改用 margin)
      padding: 1rem 0; /* just for 美观 */
    }
    .box1 {
      width: calc(8 / 12 * 100%); /* 8:12 比例 */
      background-color: pink;
    }
    .box2 {
      width: calc(4 / 12 * 100%); /* 8:4 比例 */
      background-color: green;
    }
  }
}

效果

box1 width: 600px - 16px * 8 / 12 = 389.33px

box2 width: 600px - 16px * 4 / 12 =  194.66px

注: 有微差 2px 是因为画了 border.

几个特点:

1. 它没有用 flex 的 gap 来做 gutter, 而是用 item 的 margin 累加.

2. row negative margin 用来抵消 container padding 或者 item margin (gutter)

3. 需要 parent child 鼎力合作才能完成布局.

Layout Grid by Grid

同一个题目

<div class="grid-container">
  <div class="box1">box1</div>
  <div class="box2">box2</div>
</div>

CSS Style

.grid-container {
  margin-top: 4rem;

  border: 1px solid black; /* just for 美观 */
  width: 600px; /* 限定宽度 */

  display: grid;
  grid-template-columns: minmax(0, 8fr) minmax(0, 4fr); /* 定义比例 */
  gap: 16px; /* 定义 gutter */

  .box1,
  .box2 {
    padding: 1rem 0; /* just for 美观 */
  }

  .box1 {
    background-color: pink;
  }
  .box2 {
    background-color: green;
  }
}

效果

第一个是 Flex, 第二个是 Grid. 一模一样

用 Grid 比较直观, 只要在 parent 设定好就可了. 也不需要 negative margin 这种不直观的 way.

Tailwind CSS 也是用 Grid 来实现 Layout Grid 的. 但是和上面的有一点点不同. 它像 Bootsrap 那样 parent child 鼎力合作, 但又更加灵活. 参考: Youtube – Tailwind CSS Tutorial #12 - Grids

先在 container 定义 12 column (按照 Bootstrap 的布局方式, 但其实要放多少都是可以的)

然后再 item 设置 span / line 来表示占据多少 column

 

想用 span, line 都行. 非常灵活

Simple Case by Flex

左边一个 box 固定 100px, 右边一个 paragraph 依据 container 伸缩

.container {
  display: flex;
  gap: 1rem;
  align-items: center;

  width: 300px;
  border: 1px solid black;

  .box {
    flex-shrink: 0; /* 一定要有这个, 不然它 box 也会变小 */
    width: 100px;
    height: 100px;
    background-color: pink;
  }
}

如果没有 flex-shrink: 0

效果就不对了, box 少了一大半.

Flex 的缺点就是需要动到 child 才能控制好布局.

Simple Case by Grid

同一个题目

.container {
  display: grid;
  grid-template-columns: max-content auto; /* 在 parent 控制就可以了 */
  gap: 1rem;
  align-items: center;

  width: 300px;
  border: 1px solid black;

  .box {
    width: 100px;
    height: 100px;
    background-color: pink;
  }
}

小总结

Layout Grid 和 Simple Case 从需求看都倾向于 parent 控制 child 这种布局. 所以 Grid 会更直观. 虽然 Flex 也能做到.

如果是反过来, child 控制 parent 的话 Flex 会更好一些. 通常是那种要 depend child width 的, 或者要 shrink item 的.

same button size

Flex 的特色是 item 都是 hug content, 上面这种要一样 size 的就比较适合用 Grid

display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
width: max-content;

利用 container width : max-content 就可以让 Grid container 和 Flex 一样 hug content 了.

 

总结

如果你的思路是, 有一些 item 我要排成 1 行或 1列, 那么首先 Flex.

如果你的思路是, 我有一个结构 like table, 然后有 item 要放进去, 那么首选 Grid.

通常一个项目里, Grid, Flex 都会用到的. 尽量看它们不同的地方, 发挥它们的长处即可.

Grid Layout 讲的是网格布局, Flex, Grid 都可以实现这种布局方式.