[CSS] Flex/Grid Layout Modules, part 16

Media Query 我覺得已經講到快爛掉了,搭配 Grid 說實在話也沒有很不好做的地方。不過,由於 Grid 是「方格系統」,所以你必須要撇開之前使用 Float Position, Flexbox 的那種流向的思維,這是比較令人苦惱的。

像是 Bootstrap 5.1.x 可以打開 Grid(預設關閉),揪竟多少人會打開呢?讓我們繼續看下去...


Grid 在 RWD 的使用盲點

最困難的點在於數(三聲)數(四聲)。

我就不複習 RWD 的事情了。一開始我們先複習一下 Grid 的定義方式,

.grid-layout {
    display: grid;
    
    grid-template-columns: repeat(4, 1fr);
    grid-template-rows: repeat(4, 1fr);
    
    gap: 10px;
}

由於我們上面使用了彈性單位,所以這個 Grid 在任何尺寸底下,都會彈性的保持在 4x4 的網格系統內。這樣是不是很棒?

你很棒,你全家都很棒!

換句話說,網格系統在使用彈性尺寸的設定下,無論任何尺寸都會保有最小可使用空間(min-content)的設計。這樣對於排版來說未必是一件好事。舉例來說,如果我今天想要在 [2, 3] 這個位置上,讓當中的元件強制換行,那麼要怎麼做?

假設我們在 4x4 的位置上,都恰恰好的放一個 1x1 的方塊,

// 因為我不想佔版面,所以用 SCSS 寫一點迴圈

@for $i from 1 through 4 {
    @for $j from 1 through 4 {
        .box-#{(($i - 1) * 4 + $j)} {
            grid-column: #{$j} / #{($j + 1)};
            grid-row: #{$i} / #{($i + 1)};
        }
    }
}

然後我們來找一下 [2, 3] 這個位置的方塊是誰,

現在我們找到 [2, 3] 的元件之後,那麼我要怎麼讓他 斷行 呢?你可以開始找找 Google 或是 StackOverflow 看看有沒有關於 grid layout break row 之類的結果。

你現在看到的這篇應該就是最佳解,不用找了。

首先必須澄清一件事情,網格系統裡面沒有所謂的 斷行 這件事情。你所看到或是查詢到跟斷行操作很類似的作法,其實是利用 Auto-placement 的特性去做的。

Auto-placement in CSS Grid Layout, MDN

所以,我們重新來看一次真正需要的事情什麼?

  1. 我想要在 [2, 3] 的位置發生斷行的動作。
  2. 所以 [3, 3] 之後的元件應該都繼續往下放。

好的,首先先提醒會產生的 副作用

  • 由於網格系統可擺放空間不夠,勢必會產生隱性網格。
  • 如果沒有定義自動填滿欄或列的尺寸,請留意他會使用 min-content 來填充。
  • 如果你原本有使用 負數 的軌道,請注意會被改變。

實際上該怎麼操作?

// 還記得 max-width 是數值以上?還是數值以下嗎?

@media screen and (max-width: 768px) {

    // 對了,[3, 3] 位置是 11 號的盒子
    
    .box-11 {
        grid-column: 1 / 2;
        grid-row: 4 / 5;
    }
    
    // 然後後面的 12, 13, 14, 15, 16 全部都要改
    // 然後後面的 12, 13, 14, 15, 16 全部都要改
    // 然後後面的 12, 13, 14, 15, 16 全部都要改
}

有沒有覺得很崩潰?

現在來解釋為何用 Auto-replacement 可以做到 類似 的事情,先決條件是,你的網格系統不能強迫定義每個元件的網格位置,也就是說,你必須讓他自然排列或使用相對位置排列,

.box-1 {
    grid-column: 1 / 1;
    grid-row: 1 / 1;
}

// 只設定第一個盒子,後面都不設定。
// 以上述的例子,甚至全部都不設定也可以。

如果我們要將 [2, 3] 這個位置後面的元件都換到下一行去,

@media screen and (max-width: 768px) {

    // 對了,[2, 3] 位置是 10 號的盒子
    
    .box-11 {
        grid-column: span 3;
    }
}

這樣你就會得到這樣的結果,

這就是普遍你能找到的所謂 斷行 的解法,利用 Auto-placement 的規則,加上 span 關鍵字來讓該位置的元件產生 跨欄 的動作,這樣,自然的後面的元件就會因為欄位不夠用的關係,被擠到下一行去。

說真的不叫做斷行,而是 被斷行。以現行的格線系統來看,如果不想要很麻煩的定義每個元件的位置,那麼你也只能這樣做。或是使用命名方式的 grid-template-areas 來做也不是不行。

所以我才會說這是一個數數的工作,而且很要命的是很容易數錯。

那麼,有沒有比較好的作法?有的,但我有個前提,這是我自己覺得比較好的作法,如果覺得不對或是不好的,就不要用就好了,請不要留言罵我~

如果沒問題請繼續往下看。


對於 RWD 來說,一種比較合適的 Grid 結構方式

一般情況來說,我們不太可能將整個頁面設計都使用彈性尺寸來做,除非你的客戶彈性很大(像是那個什麼石墨烯的褲子),不然通常還是會有需要固定尺寸,固定位置的需求。

以上述的例子來說,我們以樣使用一個 4x4 的網格容器,並且定義了幾個區塊,

.grid-layout {
    display: grid;
    
    grid-template-columns: 200px repeat(3, 1fr);
    grid-template-rows: 80px repeat(2, 1fr) 80px;
    
    gap: 10px;
}

.header {
    grid-column: 1 / 5;
    grid-row: 1 / 2;
}

.sidebar {
    grid-column: 1 / 2;
    grid-row: 1 / 4;
}

.main {
    grid-column: 2 / 5;
    grid-row: 1 / 4;
}

.footer {
    grid-column: 1 / 5;
    grid-row: 3 / 4;
}

這樣我們就會得到像是這樣的結構,

接著,我們來思考一下 Media Query 該怎麼做?如果我們需要在小裝置上,把 .sidebar 放到 .main 的下方,實際上的操作方式會是什麼?

首先,請記得網格所設定的固定尺寸是不會因為你的裝置變小而有所變化,也就是說,上述的設定被套用到手機上,例如 iPhone 13 的尺寸下(裝置 Viewport 寬度為 390px)的情況,那麼我們可以知道整個容器的彈性空間會被壓縮。

也就是說,扣掉 .sidebar 本身固定的 200px,再扣掉 gap10px 之後,你現在的 .main 就只剩下 180px 可用,這是彈性空間所計算出來的剩餘尺寸。然後在手機上就會出現側邊欄位比主要欄位還要大的情況。

回到 Media Query,到底該怎麼做會比較好?

// 首先,我們以 Mobile First 為出發點(個人喜好)

.header,
.sidebar,
.main,
.footer {
  grid-column: span 4;
}

// 接著做一個大尺寸的 Mediq Query

@media screen and (min-width: 768px) {
    .header {
        grid-column: span 4;
    }

    .sidebar {
        grid-column: unset;
        grid-row: span 2;
    }

    .main {
        grid-column: span 3;
        grid-row: span 2;
    }

    .footer {
        grid-column: span 4;
    }
}

由上面的例子,你應該可以理解到格線系統裡面沒有 斷行 的概念,全部都是一個矩型的矩陣在排位置,你可以把他想像成一個圍棋的棋盤,想像你要擺放的東西,在什麼位置(座標)上面,涵蓋了多大的範圍。

在 iPhone 13 的呈現結果
在 iPad 的呈現結果

那麼,我們來看看一個比較複雜的例子,倘若我們的元件排列沒那麼單純,又有順序問題的情況下,怎麼樣製作 Grid 呢(或者你直接用 Flexbox 搞不好更快)?

延續上述的例子,我們想要在 .main 的上下加上廣告,但是在手機上,這個廣告得出現在 .sidebar 的上下方,不想將 .main 夾在廣告中間。

我們先來看手機的呈現方式,

iPhone 13 呈現的結果

然後給大家看一下 HTML 的內容,

<div class="grid-layout">
    <div class="header">Header</div>
    <div class="sidebar">Sidebar</div>
    <div class="main">Main</div>
    <div class="ads-1">ADs 1</div>
    <div class="ads-2">ADs 2</div>
    <div class="footer">Footer</div>
</div>

接著我們來看一下大尺寸的呈現結果,

iPad 呈現的結果

看起來好像很完美,但實務上的操作其實挺累人的,我再強調一次,在網格系統中沒有 斷行 的概念,他只有 定位排序 可以做。

在小裝置上,我的 CSS 以最少的設定大概是這樣,

.grid-layout {
    display: grid;
    
    grid-template-columns: 200px repeat(4, 1fr);
    grid-template-rows: 80px repeat(2, 1fr 20px) 80px;
    
    gap: 10px;
}

.header,
.sidebar,
.main,
.ad-1,
.ad-2,
.footer {
    grid-column: span 5;
    order: 1;
}

.main {
    order: 2;
}

.ads-1 {
    order: 3;
}

.sidebar {
    order: 4;
}

.footer,
.ads-2 {
    order: 5;
}

接著換到比較大的裝置,你應該有發現連網格系統的尺寸都有略微調整了,

@media screen and (min-width: 768px) {
    .grid-layout {
        grid-template-rows: 80px 60px repeat(3, 1fr) 80px;
    }

    .sidebar {
        grid-column: unset;
        grid-row: span 4;
        order: 2;
    }
    
    .main {
        grid-column: span 4;
        grid-row: span 3;

        order: 4;
    }
    
    .ads-1,
    .ads-2 {
        grid-column: span 2;
        
        order: 3;
    }
    
    .footer {
        order: 5;
    }
}

Media Query 到底做了什麼?

沒有。都是在玩拼圖。

在格線系統中,其實並不像 Flexbox 會有那麼多關於文件流要考慮的點,不能說沒有,只能說是相對的少,而且要考量的點也不一樣。扣除掉使用絕對定位(position: absolute)所帶來的狀況以外,在先前的 Flexbox 並不需要考慮定位問題,這是相對的差異。

與其說是 Mediq Query 來做 RWD,倒不如說是利用 Media Query 然後把 整個畫面換掉(重做) 的感覺。不相信的話,你可以去搜尋 css grid with media queries 之類的,然後就一堆人告訴你怎麼樣用 Media Query 去換整個 Grid Layout 等等的操作。

等一下,先不要。

一般來說,把 整個畫面換掉 的事情應該是屬於 AWD(Adaptive Web Design)的範疇。最一開始的初衷應該是 規劃出一個不會整個換掉的 Grid Layout 才是重點。

總結來說,如果要在網格系統規劃 Media Query 的話,以下誠心建議:

  1. 找個方格紙筆記本。
  2. 把元件排上去,然後決定各種尺寸的順序、大小及位置。
  3. 接著決定 HTML 語意結構,你要放到 2. 之前也可,順序就要另外計算。
  4. 把最小的裝置先擺出來(通常最好做)。
  5. 慢慢把裝置放大,並在每個 breakpoint 決定元件順序、大小及位置。
  6. 根據每一種 breakpoint 規劃適當的 Grid 容器。
  7. 適當的利用彈性尺寸彌補無法預測的邊界尺寸。
  8. 真的不行就回去用 Flexbox。

既然都要用 Flexbox,為何不一開始就用?


小結

雖然說 Grid 好像用起來很潮,不過要考慮的地方其實蠻多的。目前現行的諸多套件,基本上還是把他當作 類 Flexbox 來操作,但,其實沒人在乎吧。

誰在乎誰痛苦。


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


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

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