[CSS] Flex/Grid Layout Modules, part 9

你以为网格格线告一个段落後,我会开始讲网格单元吗?当然不是啊,我们网格容器都还没讲完呢。剩下一点小东西稍微交代一下就可以了。

放心雷没有很多。


容器中的隐性轨道设定

前一篇提到隐性格线,在容器中,也有两个样式设定是 专门 给隐性轨道使用的。

样式 预设值
grid-auto-rows auto
grid-auto-columns auto

由於两个设定的可使用数值都一样,我直接拿出来外面讲比较快,

  • 相对尺寸 %
  • fr
  • min-content
  • max-content
  • auto
  • minmax()
  • fit-content()

以上这几种都是给隐性轨道使用,我快速举个例子,

.grid-container {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    grid-template-rows: 1fr;
    
    grid-auto-rows: 300px;
}
<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>

https://ithelp.ithome.com.tw/upload/images/20210912/20001433eVcjVhWxe1.png

同样的道理 grid-auto-columns 也是一样的操作方式,具体案例我就不再写一次了。


轨道尺寸详解

回到 Grid 容器轨道上,我们知道轨道除了可以设定常见的单位尺寸外,现在又多了 fr 可以使用。然而,在 Grid 容器当中,我们还有这些方法可以操作,

CSS 关键字/运算方法 说明
min-content 设定在轨道中所有最小内容单元的尺寸中,取最大的尺寸
max-content 设定在轨道中所有最大内容单元的尺寸中,取最大的尺寸
fit-content(limit) 接收一个限制数值 limit,并套入公式 max(minimum, min(limit, max-content)),其中 minimum 代表了轨道中的最小尺寸,通常会使用 auto 关键字,但多数情况下会符合 min-content 的尺寸。
minmax(min, max) 定义一组最小、最大值的区间来当作尺寸,他是一个弹性的范围。如果两个数值写反了,会直接取用最小值

其中 min-content, max-content 在实务上比较难以看出效果,针对官方所提出的演算法则来看,其实也看不太出端倪。我举一个稍微复杂一点的设定,然後我们来看看运作逻辑。

grid-template-columns: 200px 1fr max-content minmax(min-content, 100px);

假设我们的 Grid 容器有这样的栏设定,那麽计算顺序为,

  1. 容器边缘 起算。这很重要,因为 不是每个人的边缘都是左上角
  2. 边缘画出第一条线,第一个栏位是 200px 宽。
  3. 画出第二条线,第二个栏位是使用剩余空间计算 1fr 的尺寸。
  4. 画出第三条线,第三个栏位是在该轨道中任何一个 Grid 单元,取最大尺寸。
  5. 画出第四条线,第四个栏位是在该轨道中任何一个 Grid 单元,从最小尺寸中取一个最大值,当作最小值,并且以 100px 当作最大值来做范围运算。

从最小尺寸中取一个最大值 到底是什麽神逻辑?

我用实际案例解释给你看,首先我们先设计一个第一栏使用 min-content 的容器,

.grid-container {
    display: grid;
    grid-template-columns: min-content repeat(2, 1fr);
    grid-template-rows: repeat(3, 1fr);
    
    width: 100%;
    height: 500px;
}

他是一个 3x3 的容器,其他两个栏位都使用弹性空间,列的部分也全部使用弹性空间。然後我们针对第一栏的部分来放一些资料,也就是我们的 1, 4, 7 这三个地方要放一点特别的资料进去。

<div class="grid-container">
    <div class="grid-item">
        <p>放一点特别的资料进去</p>
    </div>
    <div class="grid-item">2</div>
    <div class="grid-item">3</div>

    <div class="grid-item">
        <p>放一点特别的资料进去</p>
    </div>
    <div class="grid-item">5</div>
    <div class="grid-item">6</div>

    <div class="grid-item">
        <p>放一点特别的资料进去</p>
    </div>
    <div class="grid-item">8</div>
    <div class="grid-item">9</div>
</div>

会後他会变成这样,

https://ithelp.ithome.com.tw/upload/images/20210912/20001433ylKApoqsfw.png

你的 Grid 单元内容超出 height: 500px 的设定了,对,这算是 min-content 的一个特别的地方,他所谓的 最小内容 是利用文字特性来做计算的。所以,当你使用了这项设定,那们请确保你的文字内容真的是你想要呈现的方式。

另外,你的容器若没有特定尺寸,基本上 Grid 单元还是以填满整个 Grid 容器为目标去填满的。所以如果不指定尺寸,基本上不会有上面 Grid 单元超出容器的状况。

接着,我们来加一点料进去,刚刚有说了,他是取用 在轨道中所有最小内容单元的尺寸中,取最大的尺寸 来用,那麽如果我们放了一个比较困难的文字进去,

<div class="grid-container">
    <div class="grid-item">
        <p>放一点特别的资料进去</p>
        <p>Internationalization and localization</p>
    </div>
    <div class="grid-item">2</div>
    <div class="grid-item">3</div>

    <div class="grid-item">
        <p>放一点特别的资料进去</p>
    </div>
    <div class="grid-item">5</div>
    <div class="grid-item">6</div>

    <div class="grid-item">
        <p>放一点特别的资料进去</p>
    </div>
    <div class="grid-item">8</div>
    <div class="grid-item">9</div>
</div>

那麽他的结果大家可以猜到了吗?

https://ithelp.ithome.com.tw/upload/images/20210912/200014333OzI3bq4Ie.png

那这样跟 max-content 到底有什麽差别?

别急,我们把 Grid 容器换成 max-content 再来看看到底发生什麽事情,

.grid-container {
    display: grid;
    grid-template-columns: max-content repeat(2, 1fr);
    grid-template-rows: repeat(3, 1fr);
}

其他的内容我们都不更动,我们来看看 max-content 怎麽呈现我们的结果,

https://ithelp.ithome.com.tw/upload/images/20210912/20001433KXZtEAfttN.png

这样可以稍微理解 min-contetnmax-content 之间的差异了吧。当然,这种演算方式并无法避开区块元件的占用尺寸问题。换句话说,

当 Grid 单元中有区块元件(Box Module)并指定尺寸,且超出了 min-content 的计算尺寸,那麽,这个地方的 min-content 结果就会跟 max-content 没什麽太大区别。就如同上面所描述的,所谓 min-content在轨道中所有最小内容单元的尺寸中,取最大的尺寸 来使用,所以区块元件占用,又超出计算尺寸,就会被当成是 min-content 的最终结果。

举个例子给大家看看,这次就不附上图片了,请大家自行想像一下。

<div class="grid-container">
    <div class="grid-item">
        <p>放一点特别的资料进去</p>
    </div>
    <div class="grid-item">2</div>
    <div class="grid-item">3</div>

    <div class="grid-item">
        <p>放一点特别的资料进去</p>
        <img src="...">
    </div>
    <div class="grid-item">5</div>
    <div class="grid-item">6</div>

    <div class="grid-item">
        <p>放一点特别的资料进去</p>
    </div>
    <div class="grid-item">8</div>
    <div class="grid-item">9</div>
</div>
.grid-container {
    display: grid;
    grid-template-columns: min-content repeat(2, 1fr);
    grid-template-rows: repeat(3, 1fr);
}

.grid-item img {
    display: block;
    width: 300px;
    height: 300px;
    object-fit: cover;
}

在这种状况,无论你是使用 min-contentmax-content,你的第一个栏尺寸基本上就是 300px,除非你的内文计算尺寸能超过,否则这个 300px 就会是最小(也可能是最大)尺寸。


fit-content(limit)

其实我们把他拆解出来会比较容易理解,

max(minimum, min(limit, max-content))

其中 max-content 上面有解释过了,我们只要专注在 limitminimum 这两个地方就好。首先,limit 是你传进去的数值,所以如果我们这样写,fit-content(300px) 那他就等同於,

fit-content(300px) = max(minimum, min(300px, max-content))

问题来了,那个 minimum 是什麽呢?在多数情况下,他是使用 auto 这个关键词来让装置自动决定运算尺寸,通常,在最常见的情况下,他会是 min-content 的数值。会不会有意外?我不能跟你保证永远不会。因为在 auto 的定义里面,还是有一些猫腻。

Automatic Minimum Size of Grid Items

CSS Box Sizing Module Level 3

总归一句,这边的 minimum 所使用的 auto 会采用的是取最小值的部分,而在 Grid 自动取得最小值(如 min-content 的演算法)所使用的大方向有这三种顺序,

  1. 采用特定(指定)尺寸(specified size
  2. 采用演算(转换)尺寸(transferred size
  3. 采用内容(文本)尺寸(content size
  4. 如果以上都没有则为 0

所以,我们刚刚在解释 min-content 的范例中,我们放了一张图片,那张图片对於 Grid 单元就属於转换内容的尺寸,符合第 2 点的演算方式。所以你会取得该单元的 min-content 就是内容占有的尺寸。

如果你不写 fit-content() 的话,是可以避开 min-content 的问题。就是把他拆成上面的公式就好了,如果你真的想要使用,又不想踩到 min-content 可能会发生的状况的话。

总结一句话,所谓的 fit-content() 的意思是,

类似 min-content 但如果比 min-content 还大就取比较大的。
然後如果掉入 min-content 的问题,就等於 max-content


minmax(min, max)

我们在 Part 7 提到 1fr 其实会等於 minmax(auto, 1fr) 大家还记得吗?

如果忘了可以回去 Part 7 的文章 重看一次。

然後这边的 auto 一样会有刚刚 fit-content() 的问题,就不再赘述。这个 CSS 运算方法其实很容易理解,他就只是一个取出一个范围尺寸,然後依照这个范围尺寸变动栏或者是列的尺寸。对於需要弹性应用,但又不想过大(或过小)的网格单元来说很方便。

需要注意的点就是,

  • 最大、最小值不要写反,写反等同於 minmax(min, min)
  • 容器单元指定超出此范围的尺寸不干扰网格系统
  • 容器单元指定超出此范围的尺寸不干扰网格系统
  • 容器单元指定超出此范围的尺寸不干扰网格系统

第二点很重要所以说三次!

容器单元指定超出此范围的尺寸不干扰网格系统

举例来说,

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

.grid-item:first-child {
    width: 1200px;
}

最终你会得到这样的结果,

https://ithelp.ithome.com.tw/upload/images/20210912/20001433oB5rnvRtxJ.png

所以,当你在 Grid 容器中使用 minmax() 时,请特别注意你的 Grid 单元尺寸的状况,否则他是不会排在位置上的,另外,如果你的 1fr 有与 minmax() 混用的情况,请也特别留意 Grid 单元尺寸,因为在这个时候所谓的 剩余空间 可能跟你想像的不太一样。


容器单元流向

官方对於此有一份演算法说明,

Grid Item Placement Algorithm

我觉得比较诧异的是,官方对於容器单元流向,仅说明是 针对 隐性轨道的网格单元,但是实际上是整个容器都受用,无论你是不是隐性轨道。但,其实这样说起来也是合理的,你总不可能一般轨道是一种流向,然後隐性轨道是另外一种流向吧。

控制容器单元流向只有一个样式,

样式 可用值 预设值
grid-auto-flow [row, column], dense row

rowcolumn 就很单纯,可以想成对於栏或列的 Grid 单元 优先 摆放流向。以预设值 row 来说,他就是由左至右(非 RTL 文本模式),由上到下的状况来排列。而换成 column 则是由上到下,由左至右,这种方式跟 Flexbox 在交换主要轴、交叉轴的情况很相似。

至於 dense 这个关键字,则是可以单独,或搭配 row, column 使用的,例如,

.grid-container {
    display: grid;
    
    grid-auto-flow: dense;
    /* 或是 */
    grid-auto-flow: row dense;
}

所谓的 dense 的演算方式是采用密集(紧凑)排列法,当你的 Grid 单元尺寸不同时,预设的排社方式是遇到空间不足就会往下(row 方向)或往右(column 方向)继续排列,这个时候,在某些区域就会出现空白的区块,举例来说,

.grid-container {
    display: grid;
    grid-template-rows: repeat(3, 1fr);
    grid-template-columns: repeat(3, 1fr);
    
    grid-auto-flow: row dense;
}

.grid-item:nth-of-type(1),
.grid-item:nth-of-type(2) {
    grid-column: auto / span 2;
}

https://ithelp.ithome.com.tw/upload/images/20210912/20001433NjojKW7lF9.png

此时当你使用 dense 的时候,他就会变成,

https://ithelp.ithome.com.tw/upload/images/20210912/200014332ahAMFCS6n.png

这种紧凑排版很类似以前流行过的 Masonry Layout,也就是我之前写过的 瀑布流难题,这个基本上在 Grid 上面可以得到一个还算不错的解法。


容器单元的填满

最後我们来聊填满这件事情,大家前面应该看了非常多 repeat() 的使用,对,最後我们来聊一下关於 repeat() 这件事情。他的目的就是重复你所设定的轨道,除了重复次数以外,他还有两个特殊关键字可以使用。

重复目标 使用方式
网格轨道 repeat(次数, <轨道尺寸> 或加上 <格线名称>)
固定尺寸 repeat(次数, <固定尺寸> 或加上 <格线名称>)
auto-fill repeat(auto-fill, <固定尺寸> 或加上 <格线名称>)
auto-fit repeat(auto-fit, <固定尺寸> 或加上 <格线名称>)

轨道尺寸固定尺寸 仅差在轨道尺寸可以使用 fit-content() 而固定尺寸至多能使用 minmax(),这是两者唯一差别。所以以下写法基本上都是合法的,

.grid-container {
    grid-template-columns: repeat(3, 1fr);
    grid-template-columns: repeat(3, 100px);
    grid-template-columns: repeat(3, [foo] 100px [boo]);
    grid-template-columns: repeat(3, minmax(auto, 1fr));
    grid-template-columns: repeat(3, minmax(100px, 300px));
    grid-template-columns: repeat(3, minmax(auto, 1fr));
    grid-template-columns: repeat(3, fit-content(100px));
    
    grid-template-columns: repeat(auto-fit, 100px);
    grid-template-columns: repeat(auto-fill, minmax(auto, 200px));
}

关於 auto-fillauto-fit 两者的差异,在於是否 填满 容器。我们直接用图片来解释会比较容易理解,

https://ithelp.ithome.com.tw/upload/images/20210913/20001433kArcxZ09FK.png

https://ithelp.ithome.com.tw/upload/images/20210913/20001433VFWUU9B2IW.png

两者的差异在於,

  • auto-fill 会尽可能在网格容器中填满所设定的网格轨道
  • auto-fit 网格轨道则是仅满足需要用到的网格单元

共同点是,你的 Grid 容器随时都有可能产生剩余空间。


小记

其实网格容器还是有一些小地方可以讲,但那个实在太冷门,我留到最後再提好了。


目录与小节:
[CSS] Flex/Grid Layout Modules, part 1


部落格同步放送:
[CSS] Flex/Grid Layout Modules, part 9


<<:  从零开始学3D游戏开发:模型基础 Part.1 从零开始

>>:  [Day13] Tableau 轻松学 - Rows 与 Columns

[Day 20] 两段式训练比两段式左转更安全 (迁移学习技巧)

前言 走过了资料分析、演算法选择後, 我们得知了有些可以改善模型的方向: 解决资料不平衡(Done)...

7 重做 Game struct 与 出牌方法

昨天的进度 defmodule Game do defstruct rounds: [], host...

DAY17 - [JS] 扩充功能 - 倒数计时,番茄钟

今日文章目录 需求说明 事前准备 遇到问题 参考资料 需求说明 下拉选单:选择番茄钟时间(分钟) ...

前端工程学习日记第8天

有些学生提到还要多一个 clear div 来清除会把 HTML 弄脏, 这里老师也分享一个是使用 ...

GNU Debugger

GNU Debugger,简称 GDB,是 GNU 软件系统中的除错器,由於其具有可移植的优点,在现...