40行實作響應式的佈局系統 — 告訴你col-sm-12、col-md-6 是如何實現

若你曾經使用過 Bootstrap ,肯定也在你的佈局時寫下過這類的 class ,為的是做到響應式 (RWD) 的佈局,那麼這類的佈局系統到底是怎麼做到的呢?一起來探尋背後的魔法吧。

本文會先稍微介紹 Bootstrap 裡頭的佈局系統,不過大概會是輕描淡寫的文字量(畢竟哪個前端沒用過 Bootstrap?),了解之後,再一起跟著我研究一下到底是怎麼實現的。

Demo

寫在前面

閱讀本文你應該不需要

  • 不需要什麼 Coding 能力、JavaScript 能力
  • 不需要已經看過 source code 的人,老實說我也沒看,所以你看我寫的比較像是為了符合要求所刻的。(不過文末有去對答案啦 haha)

閱讀本文你可能需要

  • 看得懂 for-loop 我會用 SCSS 幫我代勞一些重複的操作
  • 知道 SCSS 的 & 是代表嵌套他的元素
  • 知道 Flex 大概是什麼
  • 知道 media query 是什麼

本文獻給跟我一樣在書店尋找響應式的書籍,卻只看到「教你怎麼用 Bootstrap」 的你各位。

先來講講 Bootstrap 的 Grid system

我們可以稱之為網格系統柵欄式佈局,站在非前端的角度來說,並不是特別理解各個 CSS 屬性的用法。

這種時候 Bootstrap 的幫助格外的明顯 — 透過在標籤的 class 裡頭寫下非常具象化的 .row.col.col-sm-3 我們可以很簡單的把想元素佈局到對應的大小、比例中。

src. Bootstrap Document

在官方文檔中可以看到,他寫起來是長這樣的,完全不用寫到一行 CSS 。

<div class="container">
<div class="row">
<div class="col">
1 of 2
</div>
<div class="col">
2 of 2
</div>
</div>
<div class="row">
<div class="col">
1 of 3
</div>
<div class="col">
2 of 3
</div>
<div class="col">
3 of 3
</div>
</div>
</div>

除了在佈局上的幫助以外,Bootstrap 提供了一些所謂的斷點,讓我們可以讓元素做到所謂的響應式,下圖中我們把目光放在第一列。

寬銀幕(左) vs 窄銀幕(右)

我們可以注意到:

  • 小尺寸畫面中一列變多列
  • 依照比例放置在對應位置

而這樣子一套 HTML 打天下正是所謂的 RWD (Responsive web design)。

隨著近年來手機使用者大幅上升,網站開發開始著重在讓小畫面的移動裝置有更好的體驗,我想沒有人願意在觀看網站的時候一直雙指縮放吧?

廢話不多說 — 讓我們來實作吧

上頭提到兩個我們想要實現的基礎功能分別是:

  1. 佈局 — 列中多個 column 且自動撐開、撐好撐滿 (1:1:1)
  2. 響應 — 對應斷點 決定比例

佈局 — Row & Column

回頭查看我們剛剛提到最簡單的例子大概是長這個樣子:

.row{
/**
* (做些什麼好呢?)
* display : ??;
*
* (自動斷行?)
* ??????? : wrap;
**/
}

好啦其實很簡單,我只是因為左邊圖片太小湊字數而已,基本上如果你了解 flex 就知道該怎麼幹了。

.row {
display: flex;
flex-wrap: wrap;
}
.col{
flex-grow: 1;
margin: 15px;
}

這裡提一下,在不含斷點的狀況下,我們希望 column 可以「長滿」,也就是說能把整個 row 填滿,我們下了一個 flex-grow:1 的設定。由於每個 column 的 grow 都是 1 ,所以剩餘空間會被 1 : 1 : 1 : … : 1 填滿。

更新:如果你在Medium直接觀看 codepen 嵌入的樣子,可能一開始就Wrap了,試著按按看0.5、0.25或是點進去Editor吧!

另外,為了展示 flex-wrap 我有限制最小寬度為 200 px,所以你可以嘗試把畫面拉到塞不下 3 個 column 的寬度,< 600 px,溢出的 column 會被換到下一列。

好的開始是成功的第一步。

響應 (1) — 比例 col-1 ~ col-12

柵欄總共有 12 格子,依照比例看佔據幾格。

這裡我們用到 scss 一個很方便的東西,也就是 for loop,不騙你,我也是為了寫這個查了一下for loop的用法呢。

題外話,很多前端預處理器功能多樣、龐大,有時候你感覺自己用起來無感,但是在開發流程中,它總是能扮演很好的 helper guy。

讓我們把依照比例修改寬度的 CSS 填上吧:

@for $i from 1 through 12 {
.col-#{$i} {
flex-grow:0;
flex-basis: calc(-30px + 100% *#{$i}* 1/12);
}
}

這裡我們把具有設定比例的 column 的 flex-grow 取消,因為我們希望他只保持在對應比例下。

另外,們透過 flex-basis 設定每個 column 的寬度,這裡使用 CSS3 的 calc() ,在 100% 也就是父元素 (row) 的寬度下,得到對應比例的 column 寬度。(扣除 30px 源自於我們兩邊各自 margin 了 15px)

3:2:5:2

調整col-後面的數字,看看結果吧!如果一列中的 col 超過 12 格,就會被換到下一列。

響應 (2) — sm、md、lg 到底是什麼呢?

我們先不要急著翻找答案,一樣從上面的圖來想想看,它究竟代表什麼!

用一個簡單的邏輯來思考一下,通常我們會希望在大尺寸的畫面中,一列可以有多個 column ,而小尺寸中我們希望一個 column 可能就被佔滿一列,原因在於內容不想被過度縮小。

如果你是一個常常使用 Bootstrap 的工程師,應該很輕易就能明白這個道理,或是你常常在 F12 觀察其他開發者的響應式佈局邏輯的話,對以下的 class 肯定不陌生。

<div class="col col-12 col-sm-6 col-md-4 col-lg-2" />

用中文來講:

「銀幕很小是吧?給我 12 格全部佔滿!」

「銀幕適中是吧?給我佔據 6格」

…「銀幕超大是吧,那一個 column 佔 2 格就好」

諸如此類

如果總共有 6 個 column 的話示意圖如下:

// <sm
12
12
12
12
12
12
// sm
6 6
6 6
6 6
// md
4 4 4
4 4 4
// lg
2 2 2 2 2 2

那麼…所謂的很小是什麼?超大是什麼?其實在文檔裡面已經給出答案了。

那麼我們把這些條件實作到對應佈局?

道理很簡單,人人都會說,但是做出來總是需要一點小訣竅。

我們都知道依照對應銀幕尺寸來撰寫 CSS 的方法叫做 media query ,看似很簡單把 media query 的行為依序填上去就好了…問題是要填上什麼?

你的感覺沒有出錯,實際上每一個 media query 行為都是一樣的,都是如同 響應(1) 所做的,對應後面數字的比例,給他對應的寬度罷了。

再想想…再拿剛剛的圖片想想…

即使多個 class ,但我們只 care 對應尺寸下的比例有作用,拿這張圖舉例,我們在窄銀幕的時候,根本不在乎 col-lg- 後面接的數字是什麼鬼。

很好,所以我們把 sm/md/lg/xl 等等行為全部寫在對應的 media query 中…

@media (min-width: 576px) {
@for $i from 1 through 12 {
&.col-sm-#{$i} {
//grow、basis...blabla
}
}
}
@media (min-width: 768) {
@for $i from 1 through 12 {
&.col-md-#{$i} {
//grow、basis...blabla
}
}
}
.
.
.

這就是所謂的響應式斷點。

來看看效果如何吧!

尾聲

我寫的 CSS 絕對是超級髒的那種,我想 source code 本身可能運用更多預處理器的好處。

最常見的斷點佈局就這樣完成啦

不過這次的實踐至少讓我們明白幾個道理,要實踐一個配合響應式斷點做佈局的 CSS lib 可能沒有想象中的難,把握好基本的 flex 屬性就能夠做到。

另外核心代碼只有 38–87 行,且每個斷點下的行為都是一致的,但我盡量不用太多 SCSS 的語法,最硬派寫實的那種。

也許下次你想做到這類響應式佈局,不一定需要 Bootstrap。

懶人包

  1. 父元素 row 採用 flex 佈局並且允許 wrap 行為
  2. 子元素預設 grow
  3. 子元素在對應的銀幕尺寸下讓對應斷點的類名選擇器有作用
  4. 斷點選擇器幹啥?就照類名的數字比例算出寬度唄!

與 source code 對答案

https://github.com/twbs/bootstrap/blob/19aee321a027edaa60c3087bfcf6c9f1517c9b98/dist/css/bootstrap-grid.css#L279-L282

也就是說 sm / md 行為都是一樣的,只有在對應的尺寸時才會 work 這點沒有錯。

比較具有差異的是 Bootstrap 並不是像我那樣透過 calc 動態扣除 30px 邊界,因為我好像誤解了 Gutters 的意思,把 15px 設在 margin 。

https://github.com/twbs/bootstrap/blob/19aee321a027edaa60c3087bfcf6c9f1517c9b98/dist/css/bootstrap-grid.css#L1074-L1076

如果大家夠細心,就會發現我的 row 有一行意義不明的類是 justify-content-center ,其實 BS 官方真的有給這個 classname 喔!

同理框架裡頭賦予的 order、offset 也是類似看到對應的 classname 幫你代勞而已,實際上你完全可以在不使用框架的狀況下,直接透過 Flex 享受標準這個好處。水平、垂直對齊…等等。

#CSS #Bootstrap #flex #Flexbox #Grid #responsive #RWD

If any interest, 👉 https://realdennis.me.

If any interest, 👉 https://realdennis.me.