[CSS] Flex/Grid Layout Modules, part 7

Grid 剩餘空間示意圖

我們繼續來深入關於 Grid 容器的相關樣式設定。雖然目前 CSS 框架在多數情況下並不需要特別在意,但,就老話一句,誰在意誰痛苦。

踩到雷而且你還不知道為什麼


新的單位 fr

從 Grid 出現之後,我們有一個新的尺寸單位可以使用,他叫做彈性長度(flexible length),在 CSS 當中,因應這個彈性長度,所以就有了新單位 fr,而 fr 的全名為 彈性軌道flexible tracks),或者你會聽到有人稱他為 彈性係數(flexible factor

這個單位的定義與使用狀況在這邊稍微給大家解釋一下,

  1. 分配 剩餘空間
  2. 計算方式 <fr 係數> * <剩餘空間> / <所有 fr 係數總和>
  3. fr 會因為內容計算而產生不同寬度
  4. minmax() 可以用於計算 fr
  5. calc() 不能使用 fr 與其他單位數值混合運算
  6. fr 在介於 01 之間的小數時有其特殊計算方式

首先,我們來定義一下何謂 剩餘空間

Grid 剩餘空間示意圖

好的,所以我們的容器如果是一個固定尺寸的空間,我們舉個簡單的數字來方便大家裡解,

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

這樣我們會獲得一個 1000x1000 尺寸的容器,然後裡面有 4x4 總共 16 個 Grid 單元空間。由於上述的公式,我們經過簡單的四則運算,

1 * 1000px / 4 = 250px

所以我們可以很明確的知道,我們所畫出來的 Grid 單元空間有 250px 寬,然後 250px 高。在這個地方很容易理解,但,我們暫時 不考慮 Grid 單元內容會造成的影響。這個在後面提到尺寸的時候會再詳細描述。

另外,關於介於 01 之間的 fr 設定,由於這邊解釋起來相當繞口,所以我先舉幾個例子,然後大家先看看圖,後面我再來解釋到底發生了什麼事情。

首先,我們一樣使用固定尺寸空間,然後稍微換一下裡面的東西,

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

這樣,由於分配的 剩餘空間 還是使用 1000px 整份去切割,所以基本上這個還是符合我們剛剛所說的剩餘空間分配公式,

關於 1fr 的部分是:
1 * 1000px / 3.5 = 285.714px

關於 0.5fr 的部分是:
0.5 * 1000px / 3.5 = 142.857px

接著,我們來看看搭配了非彈性空間單位的情況,

.grid-container {
    display: grid;
    grid-template-rows: repeat(3, 150px) 0.5fr;
    grid-template-columns: repeat(3, 150px) 0.5fr;
    
    width: 500px;
    height: 500px;
}
搭配 0.5fr 的 Grid 容器空間配置

根據上述的公式,我們可以簡單計算 剩餘空間 如下,

500px - 150px * 3 = 50px

所以這邊我們還可以預期的到,我們的 0.5fr 是使用剩餘空間來分配,

0.5 * 50 = 25px

接著,當我們出現了第二個小數點的 fr 單位時,

.grid-container {
    display: grid;
    grid-template-rows: repeat(2, 150px) 0.5fr 0.5fr;
    grid-template-columns: repeat(2, 150px) 0.5fr 0.5fr;
    
    width: 500px;
    height: 500px;
}
當有兩個或以上的小數點 fr 設定時

當我們天真的以為,兩個 0.5fr 就會幫我把剩餘空間各半分配。但是,實際上並不會這樣運作,你會看到 Grid 容器幫你把剩餘空間全部分配完畢了。這邊的計算方式很類似 Flexbox 的 flex-grow 在介於 01 的計算方法,但稍微有點不同。

他的運作方式是,

fr 介於 01 之間時,軌道尺寸計算會使用非 100% 的剩餘空間做計算。其計算的方式,是先假定內容最大尺寸(max-content)來當作彈性係數,此時會產生一個假想的 1fr 寬度來做運算,最後再將空間分配給所設定的彈性係數,最後會得到一個 最終分配係數

如果把他想成數學的話會比較簡單,

<剩餘空間> / <fr 單位數量> * <fr 係數> = <最終分配 fr 係數>

所以我們剛剛的設定最後會得到什麼 Grid 單位空間呢?

200px / 2 * .5 = 1fr

所以,最後剩餘空間會變成兩個 1fr 去分配。而不是你所想像的,有兩個 Grid 單位空間,然後各佔一半的事情發生。

為什麼?

fr 尺寸這樣的設計,是為了在無指定容器尺寸的情況下,避免分配狀況出現問題,進而搭配 max-content 與假想 1frhypothetical 1fr size)的方式來均分剩餘空間。重點在於確保彈性尺寸在 Grid 軌道上有確切尺寸,且能還能依照所設定的比例去分配空間。

再來一個比較討厭的例子,

.grid-container {
    display: grid;
    grid-template-rows: repeat(2, 150px) 0.25fr 0.5fr;
    
    width: 100%;
}
在不指定寬度容器下,使用小數點 fr 單位分配

你可能會以為他跟剛剛一樣會把空間填滿?並不會喔!你會獲得一個 沒有分配的剩餘空間,然後基本上他雖然屬於 Grid 容器,但他 永遠不會被使用到

因為所有介於 01 之間的彈性係數設定,都僅會拿剩餘空間來做重新分配,且不會填滿(也就是非 100%)剩餘空間的計算方式,來重新分配,所以出現沒有分配的剩餘空間是很有可能發生的事情。

所以,當你在使用 fr 的時候,如果不確定你在幹嘛,請不要使用介於 01 之間的設定。

最後,來提及內文影響 Grid 單元的部分,假設我們設定了一個很簡單的 Grid 容器,

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

我們有一個很美好的 Grid 容器,然後我們請一隻貓來輸入內容,

<div class="grid-container">
    <div class="grid-item">
        我的第一個 Grid 內文
    </div>
    
    <div class="grid-item">
        我的第一個 Grid 內文
    </div>

    <div class="grid-item">
        我的第一個 GGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGrid 內文
    </div>

    <div class="grid-item">
        我的第一個 Grid 內文
    </div>
</div>
我們請貓來輸入內容

說好的 1fr 會均分呢?

誰跟你說好,格線系統並沒有跟你說一定會均分。

這個問題起源在這裡,

[css-grid] Reconsider the meaning of 1fr #1777

翻成白話的意思就是,對於 1fr 實際上的預設定義是 minmax(auto, 1fr),所以這就很顯而易見,當那個 auto 生效的時候,就會取 max-content 來當作一個尺寸。這也就是為何你的 1fr 不會均分的原因。

所以,後來就出現了 minmax(0, 1fr) 的寫法,我不知道這算不算一種 Workaround,但是,Workaround 雖然可恥,但是相當有用

.grid-container {
    display: grid;
    grid-template-rows: repeat(4, minmax(0, 1fr));
}
使用 minmax(0, 1fr) 來避開內容尺寸問題

請注意,當你使用 minmax(0, 1fr) 的時候,你的內文會被切割,請自行使用換行相關的樣式,將你的內文做適當的換行。當然,這個問題會發生在兩個軸方向上,換句話說,當你在列(row)相關的地方使用 fr 均分時,也會發生一樣的問題。

但,由於我們的文件流向大部分是由上到下,所以比較少特別限制 高度 的部分,但,這還是取決於你的使用目的,只是在此提醒一下兩個軸方向皆會發生上述的所有事情。


容器對齊與定位

上一篇沒有特別提及的對齊,這邊會連同定位再次讓大家裡解一下關於 Grid 容器對齊的事情。首先關於對齊,我們可以分成兩個面向來看,

  1. 所有 Grid 單元集合
  2. 每一個 Grid 單元
  3. 僅單一 Grid 單元
樣式 適用對象
justify-content, align-content, palce-content 所有 Grid 單元集合
justify-items, align-items, place-items 每一個 Grid 單元
align-self, justify-self, place-content 僅單一 Grid 單元
Grid 容器與 Grid 單元對齊狀況

上圖中綠色的框線代表了 所有 Grid 單元集合,換句話說,當你的容器有剩餘空間時,你所設定的 justify-contentalign-content 才會發生效果。這個情況在 Grid 單元所使用的 justify-selfalign-self 也是一樣的道理。

也就是說,當你的 Grid 單元尺寸設定,相較於整個 Grid 容器所規劃的尺寸,有產生 剩餘空間 時,才會發生效果。

另外,這邊必須要特別提及可以使用 stretch 的系列樣式,包含了,

  • justify-items
  • justify-self
  • align-items
  • align-self

當你的 Grid 單元並無指定特定尺寸時(或有產生剩餘空間時),這個 stretch 才會生效,請特別留意。

另外,在 Grid 容器整個系統內,有一個特別的設計,這個部分跟定位有關。在前幾天我們聊到 Flexbox 對於定位會打破 Flex 流向的狀況比較不同,具體上可以分為這兩種,

  1. 指定網格單元容器(Container block)的絕對定位元件
  2. 直接相對於 Grid 容器的絕對定位元件

第二點的部分跟 Flex 雷同,他會跳脫整個 Grid 生態系,但是,在不指定位置的情況下,還是會跟著整個生態係的設定走,舉例來說,

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

.grid-item:first-child {
    position: absolute;
}
當單元有定位時

而,如果你的 Grid 單元也有相關設定,如 justify-self, align-self 時,在沒有指定定位點的情況下,也是會跟著 Grid 生態係的設定呈現,

Grid 單元自身有其設定時,在不指定定位點的情況下會發生作用

接著是比較特別的網格單元容器,當你的網格單元有指定一個區域時,這個定位的效果就會發生一些變化。何謂 指定一個區域 呢?我們舉個簡單的例子來看,

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

.grid-item:first-child {
    grid-row: 2 / 4;
    grid-column: 2 / 4;
    
    position: absolute;
    top: 50px;
    left: 50px;
}

在這個時候,我們將 Grid 單元指定了所使用的 Grid 容器內的範圍,如果還不熟悉樣式寫法的人先不用緊張,我在此簡單解釋一下,

  • 指定列的範圍從網格格線 2 開始,直到網格格線 4 結束
  • 指定欄的範圍從網格格線 2 開始,直到網格格線 4 結束

接著我們定義了定位點的上邊與左邊,各設定了 50px 的距離,最終,我們會得到這樣的結果,

當 Grid 單元指定為區域時,絕對定位會相對於區域邊界處理

上圖的紅色框線部分,就是我們所指定的區塊範圍,

grid-row: 2 / 4;
grid-column: 2 / 4;    

這一點是比較特別的,當你在 Grid 單元中使用絕對定位(absolute)時,他會依照不同的情況而發生不一樣的效果。

position: fixed 無此效果,請注意!


小結

關於尺寸與定位今天就先聊到這邊,明天我們繼續來聊 Grid 生態系中的格線系統。


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


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

Hina Chen
偏執與強迫症的患者,算不上是無可救藥,只是我已經遇上我的良醫了。
Taipei