September 13, 2021

[CSS] Flex/Grid Layout Modules, part 8

[CSS] Flex/Grid Layout Modules, part 8
4x4 容器格線

我先問一個問題,如果我有一個 3x3 的 Excel 方塊,請問我有幾條格線?

Grid 容器中的格線是整個排版定位中的靈魂,但,他沒有 Excel 那麼單純。


網格格線

Grid 容器的靈魂就是網格格線,我們在宣告一個 Grid 容器的時候,每個方塊就會產生相對應的格線,而這些格線當中還會有先前提及的隱性格線(implicit grid)的設計。

我們從 Grid 容器先看起,首先我們定義一個 3x3 的 Grid 容器,

.grid-container {
    display: grid;
    grid-template-rows: repeat(3, 1fr);
    grid-template-columns: repeat(3, 1fr);
}
4x4 容器格線

當然,如果你很明確的定義你的容器,那麼他就會給你很明確的網格格線。如果你沒有、或缺少定義網格區塊的話,那麼就會落入隱性格線的設定裡面。我們這邊暫且先不談,後面會繼續提及隱性格線的相關事宜。

首先,既然格線有數字,那麼那些數字代表了什麼意思?

  • 每個 Grid 單元的 開始結束 的地方
  • 依照格線的數字可以定義一個 Grid 單元的尺寸
  • 可以使用負數
  • 可以命名
  • 命名格線請避開 -start-end 結尾

這麼一來,我如果要做一個 Grid 單元像是這樣,

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

我還沒介紹網格單元的基礎,這邊請先當作你知道這件事情(欸)。

這樣就定義出一個 Grid 單元

接著我們再回來看 Grid 容器設定,由於格線可以命名,所以我們可以這樣寫,

.grid-container {
    display: grid;
    grid-template-rows: [first] 1fr [second] 1fr [boo] 1fr [last];
    grid-template-columns: repeat(3, [foo] 1fr [boo]);
}

由於我中間混合了 repeat() 的寫法,所以他會有一點點不一樣,

命名格線

雖然說格線有了名字,但原本的數字還是可以使用的。既然有名字,那麼我們的 Grid 單元就能使用名字來決定要使用的區域,

.grid-item {
    grid-row-start: second;
    grid-row-end: last;
    grid-column-start: foo 2;
    grid-column-end: boo -1;
}

好的,關於 <命名格線> <數字> 的結構請先當作你知道這件事情(燦笑)。

命名格線設定 Grid 單元

由於我使用了 repeat() 的關係,所以你會發現同一條格線會有兩個名字,在格線的命名上這樣是合法的,也就是說一條格線可以有好幾種名字,如果你不覺得煩的話可以這樣做沒關係。

以下是奇怪的例子請不要亂用,

.grid-container {
    display: grid;
    grid-template-rows: [hello world please begin here] 1fr [second] 1fr [boo] 1fr [last];
    grid-template-columns: repeat(3, [foo boo too] 1fr [xoo]);
}

隱性格線 The Implicit Grid

這件事情其實是一種輔助的設計,當你的 Grid 容器設定並沒有辦法滿足一些使用狀況的時候,Grid 的渲染引擎會加上這種隱性格線來當作輔助,目的是用以確保整個網格系統的完整性。簡單來說,就是當你的網格元件超出了容器設定時,就會產生出隱性網格軌道,然後網格軌道就會帶著網格格線出現。

The Implicit Grid

官方的說明再貼一次,雖然不一定看得懂。

至於,哪些狀況會產生隱性網格軌道(隱性格線)呢?

合理的說法是,

  • 網格單元超出了網格容器的設計

所謂的 超出 了網格容器的設計,代表的就是在設定上的資料量,超過了原有網格容器可容納的網格單元數量。就像是 3x3 的方格,你硬要放入 4x4 的資料的意思一樣。網格系統為了確保網格軌道的正確性,就會增加軌道來放入這些資料,這些 被增加 的軌道,就是所謂的隱性網格軌道(Implicit Grid Tracks),而這些軌道的產生就會帶來了隱性網格格線(Implicit Grid Line)。

但是,

實際上(現實面)的狀況是,

  • Grid 容器設定不完整
  • Grid 單元指定了不存在的網格格線(或網格單元)
  • 使用命名 Grid 單元,卻不存在於 Grid 容器規範中

雖然說這些隱性格線目的是為了確保 Grid 容器的完整性,但是,我覺得更多的部分應該是要 防止人類隨意亂寫樣式造成的錯誤。我們可以來看看這些例子,

.grid-container {
    display: grid;
    grid-template-columns: repeat(3, 100px);
    grid-template-rows: repeat(2, 100px);
}
<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 class="grid-item">7</div>
    <div class="grid-item">8</div>
    <div class="grid-item">9</div>
</div>

在沒有定義 grid-template-rows 的情況下,所有的列(row)的產生都算在隱性軌道頭上。

隱性格線的產生

爾或者是指定了一個奇怪的網格軌道,逕而產生了隱性網格軌道,造成後面網格呈現出現不同的流向,

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

.grid-item:first-child {
    grid-column: 1 / 5;
    grid-row: 1 / 2;
}
<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 class="grid-item">7</div>
    <div class="grid-item">8</div>
    <div class="grid-item">9</div>
</div>
指定不存在的格線,所產生的隱性網格軌道

這種情況是最可怕的,由於隱性軌道跟一般網格軌道的功能是一樣的,所以,當你原本設想的網格容器 3x3 的區域,如果因為設定錯誤,他就會變成 4x3 的網格容器。是的,網格系統很貼心的幫你把你要的區域給畫出來了。

驚不驚喜,意不意外。

另外,在容器使用 grid-template-areas 的設定方式時,由於指定名稱與其網格單元名稱的配對不合(或沒有配對),也會產生隱性格線,而且,在更複雜的網格單元設定時,更容易會造成不必要的隱性格線產生。

這邊必須要先提及,grid-template-areas 有一個 附加特性

所有命名區塊的位置,都會產生 4 條隱性命名格線,也就是欄(row)的方向兩條,列(column)的方向兩條。

我們先看範例,後面再來講解到底為什麼。這個時候,我們還是請一隻貓來切版,

.grid-container {
    display: grid;
    grid-template-areas:
        "nav nav nav"
        "sidebar main main"
        "sidebar main main";
        
    width: 500px;
    height: 500px;
}

.nav {
    grid-area: nav;
}

.sidebar {
    grid-area: sidebarrrrr;
}

.main {
    grid-area: main;
}
<div class="grid-container">
    <nav class="grid-item nav">1</nav>
    <aside class="grid-item sidebar">2</aside>
    <main class="grid-item main">3</main>
</div>

最終的結果會是這樣,

命名網格單元配置出錯時會多出兩條隱性網格格線

命名網格最可怕的地方在於,你的整個 Grid 容器尺寸不變,然後會依照比例 均分 這些網格格線所劃分出來的區域,無論你是不是隱性網格格線都一視同仁。

之所以名字打錯會造成這個問題,主要還是命名網格單元設定的 附加特性 有關。以上述的例子來看,我們單純看欄(column)的部分就好,畢竟是正方形轉過去就可以通了。

首先是 nav nav nav,在欄方向有兩條線,名稱分別是,

[nav-start], [nav-end]

在列方面也會有兩條線,名稱跟欄一樣。其他的區塊也分別都會有四組網格格線,如同我剛剛所描述的。那麼,我們再來看看那個不存在於命名區域設定的網格單元 sidebarrrrr

首先,他一樣會有四條線,

欄方向 [sidebarrrrr-start], [sidebarrrrr-end]
列方向 [sidebarrrrr-start], [sidebarrrrr-end]

上面的計算結果,我們發現網格格線的計算數字被標記到了 6,原本只有 4 而已,原因是,你一個隱性命名網格單元,每個方向都會產生 2 條線,然而,他並不會將 上一個 網格單元的最後一條線做合併的動作,換句話說,

無論是一般命名網格單元還是隱性命名單元,他的網格格線都是獨立的。

所以我們從欄方向來看 sidebarrr 這個單元,就很容易理解為何變成 6。因為對於 nav nav nav 這個命名區塊來說,他的網格格線為 [nav-start], [nav-end],但由於隱性命名單元的發生,所以,單就 nav nav nav 這個區塊來看,他的網格格線就變成了,

[nav-start], [nav-end], [sidebarrrrr-start], [sidebarrrrr-end]

由於 [nav-start] 等同於 網格格線數字 1,而 [nav-end]4,所以後面增加的隱性命名區塊網格格線,就變成了 56 了。

當然,你不要打錯名字就好了。

另外,如果在命名 Grid 單元上,使用了錯誤的網格區域設定,雖然不一定會造成隱性網格區域的產生,但一樣會打壞原本網格單元擺放的邏輯。


使用命名格線與隱性格線的雷

前面有提到了格線可以取名字,但是不建議你的名字當中有 -start-end 結尾。原因在於,在 Grid 格線系統中,他會使用 -start-end 這兩個關鍵字來組合你的網格名稱,用以找到確切的網格格線位置。

又是一個小貼心,然後雷死你不償命的。

我們舉一個實際的例子來看,

.grid-container {
  display: grid;
  grid-template-columns: [first] 100px [foo foo-start] 100px [foo-end] 100px [last];
  grid-template-rows: repeat(2, 100px);
}

.grid-item:first-child {
    grid-column-start: first;
    grid-column-end: foo;
    
    grid-row: 1 / 2;
}

聰明如你,一定會覺得,阿不就是把第一個 .grid-item 放在第一格 100x100 的地方,這有什麼好困難的?

來,我們來看實際的結果,

-start 與 -end 命名造成的問題

驚不驚喜,意不意外。
我雷死你這小王八蛋!

這是網格格線一個不知道是不是因為隱性命名網格區域會用到 -start, -end 的關係,索性將這兩個字也納入了一般命名網格格線的規則裡。為什麼?我上面之所以把 grid-column 分開來設定的原因,就恰巧可以解釋這件事情。

  • grid-column-start 如果使用命名格線,他會去找看看有沒有叫做 <我格線>-start 的命名格線,如果找不到才會先去找格線名字相同的,例如 <我格線>
  • grid-column-end 跟上面的邏輯一樣邏輯同上,只是後面改成 <我格線>-end
  • grid-row-start, grid-row-end 邏輯同上。
  • grid-column, grid-row 這兩個縮寫的邏輯也同上。

所以,我上面的例子是這樣命名的,

grid-template-columns: [first] 100px [foo foo-start] 100px [foo-end] 100px [last];

依據網格格線的規則,他會先去找 [foo-end],所以就會有上面圖片中的效果。

為什麼不是先找 foo 而是先找 foo-end

w3c 官方說的,不爽你可以去他 Github 發 PR(欸

Line-based Placement
First attempt to match the grid area’s edge to a named grid area: if there is a grid line whose line name is <custom-ident>-start (for grid--start) / <custom-ident>-end (for grid--end), contributes the first such line to the grid item’s placement.

所以說,沒事請不要亂用 -start-end 來命名你的格線,他會 優先配對

另外,如果使用了不存在的格線名稱,那麼你的格線系統就會多一個尺寸為 0 的軌道,然後多一條線出來。跟剛才的命名單元一次多兩條不一樣。這個部分一樣會破壞 Grid 原本擺放單元的位置,舉例來說,

.grid-container {
  display: grid;
  grid-template-columns: [first] 100px [foo] 100px [boo] 100px [last];
  grid-template-rows: repeat(3, 100px);
}

.grid-item:first-child {
    grid-column-start: first;
    grid-column-end: qoo;
    
    grid-row: 1 / 2;
}
不存在的網格格線名稱造成的隱性格線

總括來看,其實我並沒有特別覺得網格系統很貼心,而這些真的只是為了維持網格展示能正常的一些補救(防呆防蠢不防雷)的作法。

對於不明就裡的人來說,被雷到應該只是剛好。


小結

其實講下來,應該跟格雷的陰影差不多。格線如果你都很規矩的操作,基本上不太容易出亂子。


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


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