40行實作響應式的佈局系統 — 告訴你col-sm-12、col-md-6 是如何實現
若你曾經使用過 Bootstrap ,肯定也在你的佈局時寫下過這類的 class ,為的是做到響應式 (RWD) 的佈局,那麼這類的佈局系統到底是怎麼做到的呢?一起來探尋背後的魔法吧。
本文會先稍微介紹 Bootstrap 裡頭的佈局系統,不過大概會是輕描淡寫的文字量(畢竟哪個前端沒用過 Bootstrap?),了解之後,再一起跟著我研究一下到底是怎麼實現的。
寫在前面
閱讀本文你應該不需要…
- 不需要什麼 Coding 能力、JavaScript 能力
- 不需要已經看過 source code 的人,老實說我也沒看,所以你看我寫的比較像是為了符合要求所刻的。(不過文末有去對答案啦 haha)
閱讀本文你可能需要…
- 看得懂 for-loop 我會用 SCSS 幫我代勞一些重複的操作
- 知道 SCSS 的 & 是代表嵌套他的元素
- 知道 Flex 大概是什麼
- 知道 media query 是什麼
本文獻給跟我一樣在書店尋找響應式的書籍,卻只看到「教你怎麼用 Bootstrap」 的你各位。
先來講講 Bootstrap 的 Grid system
我們可以稱之為網格系統、柵欄式佈局,站在非前端的角度來說,並不是特別理解各個 CSS 屬性的用法。
這種時候 Bootstrap 的幫助格外的明顯 — 透過在標籤的 class 裡頭寫下非常具象化的 .row
、 .col
、 .col-sm-3
我們可以很簡單的把想元素佈局到對應的大小、比例中。
在官方文檔中可以看到,他寫起來是長這樣的,完全不用寫到一行 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 提供了一些所謂的斷點,讓我們可以讓元素做到所謂的響應式,下圖中我們把目光放在第一列。
我們可以注意到:
- 小尺寸畫面中一列變多列
- 依照比例放置在對應位置
而這樣子一套 HTML 打天下正是所謂的 RWD (Responsive web design)。
隨著近年來手機使用者大幅上升,網站開發開始著重在讓小畫面的移動裝置有更好的體驗,我想沒有人願意在觀看網站的時候一直雙指縮放吧?
廢話不多說 — 讓我們來實作吧
上頭提到兩個我們想要實現的基礎功能分別是:
- 佈局 — 列中多個 column 且自動撐開、撐好撐滿 (1:1:1)
- 響應 — 對應斷點 決定比例
佈局 — 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 填滿。
另外,為了展示 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)
調整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。
懶人包
- 父元素 row 採用 flex 佈局並且允許 wrap 行為
- 子元素預設 grow
- 子元素在對應的銀幕尺寸下讓對應斷點的類名選擇器有作用
- 斷點選擇器幹啥?就照類名的數字比例算出寬度唄!
與 source code 對答案
也就是說 sm / md 行為都是一樣的,只有在對應的尺寸時才會 work 這點沒有錯。
比較具有差異的是 Bootstrap 並不是像我那樣透過 calc 動態扣除 30px 邊界,因為我好像誤解了 Gutters 的意思,把 15px 設在 margin 。
如果大家夠細心,就會發現我的 row 有一行意義不明的類是 justify-content-center ,其實 BS 官方真的有給這個 classname 喔!
同理框架裡頭賦予的 order、offset 也是類似看到對應的 classname 幫你代勞而已,實際上你完全可以在不使用框架的狀況下,直接透過 Flex 享受標準這個好處。水平、垂直對齊…等等。
#CSS #Bootstrap #flex #Flexbox #Grid #responsive #RWD