September 14, 2021

[CSS] Flex/Grid Layout Modules, part 9

[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>
設定隱性軌道尺寸

同樣的道理 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>

會後他會變成這樣,

使用 min-content 會發生的情況

你的 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>

那麼他的結果大家可以猜到了嗎?

min-content 取最小值中最大的來使用

那這樣跟 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 怎麼呈現我們的結果,

max-content 的呈現方式

這樣可以稍微理解 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;
}

最終你會得到這樣的結果,

minmax() 不會採納單元尺寸

所以,當你在 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;
}
一般的排列會有單元空間不足的情況

此時當你使用 dense 的時候,他就會變成,

使用 dense 緊湊排列

這種緊湊排版很類似以前流行過的 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 兩者的差異,在於是否 填滿 容器。我們直接用圖片來解釋會比較容易理解,

auto-fill 會盡可能填滿 Grid 容器
auto-fit 僅會填滿到內容結尾

兩者的差異在於,

  • auto-fill 會盡可能在網格容器中填滿所設定的網格軌道
  • auto-fit 網格軌道則是僅滿足需要用到的網格單元

共同點是,你的 Grid 容器隨時都有可能產生剩餘空間。


小記

其實網格容器還是有一些小地方可以講,但那個實在太冷門,我留到最後再提好了。


目錄與小節:
[CSS] Flex/Grid Layout Modules, part 0


鐵人賽同步放送:
https://ithelp.ithome.com.tw/articles/10263009