跳至主要内容

網格系統

8/21/2023 發布

在 2022 年我決定以前端工程師作為轉職目標後,就開始了這段不歸路漫漫長路。
我一開始選用的教材是 Huli 大大開設的 Lidemy 網站課程,
以及參考他 bootcamp 的課綱,按表操課自學,只是沒有老師批改......

經歷六角學院在 2022 年末舉辦的精神時光屋活動後,挫折還蠻大的,
每週主題我都嘗試用 React 開發,想藉此熟悉 React,
但沒有使用過其他可以搭配的 CSS 框架,僅使用在課綱內學過的 styled-components,
所以幾乎是以純手刻的方式做開發。

因為沒有前期規劃的概念,導致三週的挑戰都沒有時限內完成,
經常需要邊改樣式邊整合 React 邏輯,既沒有效率,最終呈現也不盡人意,
等於版面與功能我都沒有兼顧好。

為何需要網格系統

後來我還是有補完精神時光屋的題目,但對於切版的困惑還是沒有停止,
直到我看到卡斯伯老師於去年線上直播分享的主題:從 Figma 到 VSCode,設計做到網頁切版
才知道切版的前期規劃中,有一件非常重要的事被我忽略了:

網格系統

大部分設計稿會照網格系統做出來,所以網頁內許多元素的排列其實是有規律的,
沒有這些前備知識的話, 拿到設計稿可能會毫無頭緒從何開始,
比如各個區塊的大小、間距、RWD 變化方式等等。

或是寫了一卡車屬性,但不知從何整理,
常常想到什麼就寫什麼,最後發現明明是相似的結構,
卻重複寫了好幾個差不多的 class...

以網格系統的概念拆解設計稿的各個區塊,實作過程就具備規律,後續修改也比較容易,
如果設計稿本身是沒有網格系統的,也可以自己加入,方便開發上的管理,
專案開發的常用的 CSS 框架或是 UI 庫大多也都有內建網格系統,
如 Bootstrap、Tailwind 等等,不過我們可以先想想看 :

**純手刻要怎麼做到網格系統?

嘗試寫出簡單的網格系統後,使用框架時也會更有概念,
我自己使用的這套邏輯也是因為大學上課曾經使用過 Bootstrap 而參考它做出來的 XD
但手刻過一遍再運用到切版作業上,也變得比較清楚網格系統背後的設計脈絡。


Container

以 PCHOME 首頁為例,可以發現從畫面最頂端導覽列的文字,
到下面的圖卡、文字等,它們距離畫面左右邊界的距離是一樣的,
這是因為這些元素都放在容器(container)裡面:

PCHOME 首頁

在遇到螢幕較寬的情況下,設計上會希望內容不會因為螢幕的寬度變寬,
導致圖片或區塊的編排也被橫向拉扯,這時會設定最大寬度來保證元素不會超出這個範圍。

如設計稿桌機開版為 1920px、平板 768px、手機 375px,
設計師就可能就會給容器的最大寬度為:1296px、696px、348px 等等的,
常見的數字大多是 Bootstrap、Tailwind 這些框架的斷點,方便前端直接套用開發。

假如我從手機畫面開始切版,容器就會這樣寫:

// 在 767px 以前都顯示手機畫面
.container {
  margin: 0 auto;
  max-width: 348px;
}

// 768px ~ 1199px 為平板畫面
@media (min-width: 768px) {
  max-width: 696px;
}

// 1400px 開始為桌機畫面
@media (min-width: 1400px) {
  max-width: 1296px;
}

斷點的寫法就因人而異,不過原則都是一樣的:

  1. 如果能用最少的斷點作出所有版型是最好的
  2. 無論怎麼切最後都要在設計稿指定的解析度上呈現一樣的結果
  3. 斷點之間中間的過度範圍過大,要跟設計師確認一下需不需要提早變版
    (像上面平板 768px 跟桌機 1920px 中間區間有點大,因此我設在 1400px 開始變為桌機版本)

Column

決定好容器的寬度後,容器內會依版型分成 12 格~ 4 格(column)等等,
手機通常是 4 格、桌機 12 格,平板就比較不一定,各種格數都有人設計過。

有採用網格系統的稿件,元素寬度大多數也會依循格子數量做設定或排列,
比如下面的設計稿可以看到,導覽列的 logo 設定為 1 格寬,
畫面右邊的大 logo 與按鈕則是佔了 4 格,並且要在第 8 格出現:

2022F2E UI 組小菜的稿件

格子之間會有一個間距而不是緊緊相連的,設計稿通常也會設定這個間距的大小,
稱為 gutter,gutter 在不同的版面設計上有可能是不同寬度的,所以在撰寫時要注意。

知道這些概念之後,我們就可以設定格子的 class,   以下是桌機和手機版型的大概示範:

// 格子間距
$gutter: 20px;

// 設定手機版的格子寬度 總共 4 格
@for $i from 1 through 4 {
  .col-sm-#{$i} {
    width: calc((100%-($gutter * 3)) / 4 * $i + $gutter * ($i-1));
  }
}

// 設定桌機版的格子寬度 總共 12 格
// 寫在 media query 裡面保證它只會在某個寬度下作用
@media (min-width: 1400px) {
  @for $i from 1 through 12 {
    .col-lg-#{$i} {
      width: calc((100% - ($gutter * 11)) / 12 * $i + $gutter * ($i - 1));
    }
  }
}

格子寬度的設定跟數學課的種樹問題是類似的,   大家不要緊張,我馬上解釋......

  1. for 迴圈來產生一連串的 class 名稱
  2. width 這個 CSS 屬性可以透過 calc() 做百分比和 px 等各種單位的混合計算
  3. 100% 代表整個容器的寬度
  4. 所以分散成 12 格並不是直接用 100% 除以 12,要先扣掉格子之間的間距 gutter
  5. 看不懂直接抄沒關係

以桌機為例,有 12 個格子會有 11 個間距,
扣掉全部間距佔用的寬度 $gutter * 11,再除以 12,
就是容器的寬度下每個格子真正的寬度,
再依照目前迴圈執行的數字來算出 1 ~ 12 格個別有多寬。

假如現在迴圈跑到 $i = 6,也就是正在生成 .col-lg-6 這個 class,
從剛剛算出的每格寬度 ( 100% - ( $gutter * 11 )) / 12,
乘上 6 再加上 5 個間距,就是 .col-lg-6 實際上在畫面佔用的寬度。

透過 for 迴圈加上一些簡單的數學計算,
就完成了 1 到 12 格寬度計算,並且可以使用 class 設定寬度。

因為透過 @media 限制了每種版型下會作用的格子,
所以一個元素上可以加 .col-lg-4col-md-6 等等多種 class,
使它們可以符合設計稿上不同版型下的寬度變化!

但要注意的是如果該元素已經有 col 的 class,
在 CSS 裡面就不要再為這個元素設與寬度有關的屬性,
否則會因為複寫的關係,使 col 計算好的寬度失效。

因為容器有設定 max-width,所以畫面寬度在不超過容器的最大寬度下伸縮時,
容器本身的寬度也會不斷改變,而我們剛剛在計算格子寬度時是以容器的 100% 為基準,
所以這些有 col 的元素也會等比例拉伸。


Row

有了容器與格子後,就可以使用 flex 來輕鬆處理結構比較單純的 RWD 變版,
熟悉 Bootstrap 的人應該知道,擁有 col 的元素都要放在 row 裡面:

.row {
  display: flex;
  flex-wrap: wrap;
}

row 就是一個有 flex 屬性的容器,必須搭配上 flex-wrap: wrap 的屬性,
才能使我們設定好的元素佔有正確的格子寬度並成功換行。   (沒有加入 wrap 會全部擠在同一行)

例如我們設定四個一排的元素,每個元素它們在桌機、手機上各佔 3 格、4 格,   在畫面上看起來是:

  • 桌機上四個元素排一橫列佔滿了 12 格。
  • 手機上每個元素都滿版,呈現直行排列。

(因為前面設定手機是 4 格,所以如果元素是 4 格寬就是滿版了)

兩個版型的 row 變化

這時 row 或是其他具有 flex 屬性的容器,搭配 gap 屬性就可以起到很好的分隔效果,
這些元素會依照 gap 的數值分開,並且頭尾元素也不會產生額外的間距。

(gap 的左右間隔通常就是網格的 gutter,上下間隔就看稿件怎麼設定)


建立撰寫規則

純手刻的模式下,當我們寫的區塊越來越多,
CSS 檔也會變得冗長,即使用 mixin 的方式把重複的屬性功能打包起來,
有可能還是整理不完,這時候如果有一套撰寫規則,就能讓閱讀的難度稍微降低!
如果是多人協作的話,訂製一個大家必須遵守的規則即可。

如像我自己撰寫時,通常是依屬性的性質,下個空行區分:

div {
  // 跟 position 有關的寫在一起
  position: relative;
  top: 100px;
  left: 50px;

  // 跟 display 有關的
  display: flex;
  flex-direction: column;
  justify-content: center;

  // 跟 box model 有關的
  margin: 0 auto;
  padding: 12px 20px;
  width: 50%;

// 跟 border 有關的
  border: 1px solid red;

  // 跟 background 有關的
  background-color: grey;
  opacity: 0.8;

  // 跟 font 有關的
  color: white;
  font-size: 20px;
  text-align: right;

  // 跟 transform 有關的
  transform: scaleX(0.5);
  transition: 0.3s;

  // 其他有的沒的
  object-fit: cover;
}

這是我個人的撰寫習慣,僅供參考~只要有一套方便自己管理和修改的寫法即可。

要注意的是當用巢狀選擇器的方式來寫屬性時,
盡量控制在 3 層以內,否則會造成網頁的效能問題,
元素性質比較特別或是作用範圍較大的,
例如資訊結構較多的圖卡、或是大區塊裡面的元素有互動功能等等,
最好也要另外命名一個 class 來直接選取它,
以上兩點也會都會影響閱讀程式碼的困難度。


參考資料: