首先,設計你的刪格.
你是要使用等寬的還是不等寬的網格列?要有多少列數?間隔和列的大小是多少?
當你回答了上面的問題你只能做出正確的網格計算。為了解決大家的困擾,我寫了一篇設計刪格。如果你正想學習設計一個刪格系統可以讀一下。
其次,你需要明確你的刪格系統在不同視口的表現
當屏幕視口發生變化時你要實時重新計算列和間隔么?當間隔保持不變時,你要改變列的大小么?在明確的分界點上你要改變刪格列的數量么?
你需要好好回答這些問題。在如何計算列寬和間隔寬上,這些會給你一些線索。在提到的那篇設計刪格中我也寫了這些要考慮的東西,你不確定這些問題的話可以參考下。
最后,你願意在HTML里寫刪格類嗎?.
當涉及到刪格系統時,前端世界拆分出兩個派系。
一派就是在HTML中寫入刪格類名(以Bootstrap和Foundation為代表)。我稱這一派HTML刪格系統。HTML像下面這樣:
<div class="container">
<div class="row">
<div class="col-md-9">Content</div>
<div class="col-md-3">Sidebar</div>
</div>
</div>
第二個派系主張在CSS中創建刪格。我稱之為CSS 刪格系統。
CSS刪格系統的HTML代碼相比HTML刪格系統的要簡單一點。對於同樣的頁面你需要創建的標簽也會少一點。同時,你也不需要記住這些刪格類名是什么:
<div class="content-sidebar">
<div class="content"></div>
<div class="sidebar"></div>
</div>
另一方面,CSS刪格系統中的CSS更復雜。你需要仔細想清楚才能取得一個簡單的解決方案(如果你從沒創建過)。
我會選擇什么?
許多前端大牛會選擇CSS刪格系統。我,也不例外,屬於CSS刪格派系(當然我不敢稱自己是大牛)。
關於我為什么選擇CSS刪格系統而不是HTML刪格系統,我寫過另一篇文章,如果你感興趣可以看下。我還寫了從HTML刪格到CSS刪格,如果你有興趣做個轉換,這篇可以幫你從HTML刪格系統遷移到CSS刪格系統。 (這么多文章… 😢)
總之,這就是在建立刪格系統之前你需要明確的三件事。概括下就是:
-
系統設計
-
在不同視口的刪格表現
-
是否使用HTML或者CSS刪格系統
如果我們有了這些必要知識我們只需要繼續前進就行了。這篇文章的剩下部分就是我們接下來要做的事情:
-
這個刪格系統有一個1140px的最大寬度,具備12個75px寬的列以及20px的間隔。(參考設計刪格里如何獲得這些數值的提示)
-
當視口大小變了,在間隔保留20px固定大小的同時應該實時重新計算列寬大小。(在設計刪格中我提到了為什么會選擇這種展現形式)
-
我打算使用CSS刪格系統(在這篇文章中有我為什么建議用CSS刪格的原因)
有了這些,讓我們開始吧!
建立刪格系統
建立刪格系統有八個步驟,概括如下:
-
選擇一個標准來創建刪格寬度
-
設置
box-sizing
為border-box
-
創建一個刪格容器
-
計算列寬
-
決定間隔定位
-
創建一個調試網格
-
讓布局變化
-
讓你的布局響應變化
一旦你想明白了,這八個步驟大部分是相對明確簡單的。我會展開每個步驟詳細說明其中你需要知道每個事項。
Step 1: 選擇一個標准
使用CSS刪格,FlexBox,或者簡單原始的floats來創建刪格?對每個標准來說你的考慮事項以及實現細節都會有所不同。
CSS刪格是到目前為止三種標准中創建網格最好的工具(因為是網格嘛 😎)。遺憾的是,現在支持CSS刪格會留下更多需要被解決的問題(原文:support for CSS grid leaves more to be desired right now)。每個瀏覽器將CSS刪格布局隱藏在一個標志后面,這就是為什么我們不會在這篇文章中去碰它。如果你對CSS刪格有興趣,我強烈建議參考Rachel Andrew的作品。
接下來,我們來看看Flexbox和Floats。使用這兩個標准的注意事項是相似的,所以你可以挑一個繼續跟着這篇文章走下去。這里我將使用Floats,因為它對初學者來說更容易解釋理解。
但是如果你選擇用Flexbox,請記住這里有一些細微的差別你需要調整。
Step 2: 設置box-sizing
為border-box
box-sizing
屬性可以改變瀏覽器用來計算width
和height
屬性的默認CSS盒模型。通過改變 box-sizing
為border-box
,我們可以更輕松地計算列和間隔的大小(你接下來就會明白為什么)。
下面這張圖,總結了width
在不同的box-sizing
值下是如何計算的。
box-sizing屬性及其如何影響寬度計算
在一個網站上,我通常將所有元素的box-sizing
值都設為 border-box
,這樣width
和 height
計算全部都保持了一致(還有直觀)。就這樣:
html {
box-sizing: border-box;
}
*,
*:before,
*:after {
box-sizing: inherit;
}
提示:如果你需要一個更深入理解box-sizing
,我建議你看 這個.
Step 3: 創建一個刪格容器
每個柵格有一個決定了它最大寬度的容器。我把它取名為.l-wrap
。這個.l-
前綴表示layout, 這個是我自從讀了Jonathan Snook的SMACSS之后一直使用的命名約定。
.l-wrap {
max-width: 1140px;
margin-right: auto;
margin-left: auto;
}
注意:出於可訪問性和可響應的目的,我強烈建議使用相對單位比如em
或者rem
而不是像素。但這篇文章中,我都使用了像素,因為這比較容易理解。
Step 4: 計算列寬
記住,我們使用floats來創建列和間隔。用它的時候,我們只有五個屬性可以用(如果用flexbox的話會多一些);這五個是:
-
width
-
margin-right
-
margin-left
-
padding-right
-
padding-left
如果你還記得,CSS柵格系統的HTML是類似這樣的:
<div class="l-wrap">
<div class="three-col-grid">
<div class="grid-item">Grid item</div>
<div class="grid-item">Grid item</div>
<div class="grid-item">Grid item</div>
</div>
</div>
從這段HTML中我們了解到這個網格有一行三列,也沒有額外的創建間隔的元素,這就是說:
-
創建列需要
width
屬性 -
創建間隔用
margin
或者padding
屬性
如果我們同時思考列和間隔,問題就會變得復雜。所以讓我們假設我們先創建一個沒有間隔的網格。
這樣一個網格的輸出就會像這樣:
沒有間隔的三列網格
這里就是我們需要進行一些計算的地方了。我們知道網格有一個1140px的最大寬度,那么每一列的就是380px(1140 ÷ 3
)。
.three-col-grid .grid-item {
width: 380px;
float: left;
}
到目前為止,一切都很順利。我們創建了一個在大於1140px的屏幕視口上可以良好展示的網格。但不好的是,當視口小於1140px時就不能正常展示了。
小於1140px時網格就成這樣了
這就意味着對於網格列我們不能使用像素作為單位。我們需要一個單位可以隨視口變化而變化。
這就是說我們不能使用像素作為我們的度量方法。我們需要一個可以根據容器寬度重新計算的單位。唯一可以實現這種方式的單位就是百分比(%
)。所以,我們用百分比的方式定義寬度:
.three-col-grid .grid-item {
width: 33.33333%;
float: left;
}
上面的代碼可以得到一個沒有任何間隔的簡單三列網格。當瀏覽器窗口大小改變時,這三列會按比例改變。
沒有間隔的三列網格
在我們繼續之前還有一件事。每當所有的子元素在容器中浮動時,容器的高度就會塌陷。這個現象稱為 浮動塌陷。就好像這個容器里不包含任何子元素。
浮動塌陷(圖片來自CSS Tricks)
為了解決這個問題,我們需要一個clearfix,像這樣:
.three-col-grid:after {
display: table;
clear: both;
content: '';
}
如果你使用像Sass那樣的預處理,你可以轉換到一個mixin里,這樣能讓你在不同的地方方便引入同樣的代碼。
// Clearfix
@mixin clearfix {
&:after {
display: table;
clear: both;
content: '';
}
}
// Usage
.three-col-grid { @include clearfix; }
只要完成了列數的部分,接下來就是建立一些間隔了。
Step 5: 決定間隔位置
目前看來,我們知道應該用margin
或padding
來設計間隔。但是我們應該用哪一個呢?
如果你概覽一下,就會快速注意到你有四種可能的方式來設計這些間隔。
-
間隔可以通過margins放置在一邊
-
間隔可以通過paddings放置在一邊
-
間隔可以通過margins被相等的分隔在兩邊
-
間隔可以通過paddings被相等的分隔在兩邊
創建列和間隔的4種方式
從這里就開始變得復雜了。你需要用不同的方式計算列的寬度,這取決於你用的方法。
我們將會一個一個的講這些方法,看看它們的不同之處。慢慢來體會吧。
開始咯:
Method 1: 單邊間隔(Margin)
用這種方法,是通過margin
屬性來創建間隔。這個間隔是在列的左邊還是右邊,這取決於你選擇哪邊。
為了讓初學者明白,假設你選擇將間隔放在右邊,那么你將要做的是:
.grid-item {
/* Need to recalculate width property */;
margin-right: 20px;
float: left;
}
根據這張圖你要重新計算列寬:
使用margins實現的單邊間隔
從這張圖上可以看到1140px等於三列和兩個間隔。
這里有個問題...我們需要用百分比表示列寬,但間隔固定是20px,我們沒法同時用兩個不同的單位進行計算。
好吧,這在以前是不太可能,但今非昔比了。
你可以使用CSS的calc
方法將其他單位和百分比混合使用。它會重新得到百分比的單位值來快速執行計算。
這樣一來你可以將width通過一個函數得到,然后瀏覽器就會自動為你計算這個值:
.grid-item {
width: calc((100% - 20px * 2) / 3);
/* other properties */
}
很棒吧!
得到列寬后,你需要刪除來自最右邊網格列的最后一個間隔。你可以這樣寫:
.grid-item:last-child {
margin-right: 0;
}
多數時候,當你去掉最右邊項的最后一個間隔時,你也會想將它浮動在右邊防止子像素舍入錯誤將最后一列放到了下一行導致把網格搞的一團糟。這只是會發生在子像素向上舍入的瀏覽器中。
子像素舍入錯誤可能會將最后一項放到下一行造成網格混亂。
.grid-item:last-child {
margin-right: 0;
float: right;
}
唉,終於講到這里了。還有件事。
到現在為止,代碼還算不錯,如果你的網格只包含一行。但是,如果有多行,這些代碼可不會剪切網格。
如果有多行,我們的代碼就行不通了。 我們需要做的是去掉每一行的最右邊項的右邊距。實現這種最好的方式是用nth-child()
:
/* For a 3-column grid */
.grid-item:nth-child(3n+3) {
margin-right: 0;
float: right;
}
這就是用margins實現單邊間隔你所需要的所有代碼。在codepen上你可以看看。
查看 CodePen上用margins實現的單邊間隔網格 by Zell Liew (@zellwk).
注意:Calc方法在IE8和Opera mini上不起作用。如果你需要支持這兩種瀏覽器的話,可能得考慮其他方法。
Method 2: 單邊間隔 (Padding)
和用margins實現的單邊間隔類似,這種方式也是要求你將間隔放在列的一邊。假設還是選擇的右邊。
.grid-item {
/* width property */
padding-right: 20px;
float: left;
}
然后,你可以通過這張圖重新計算列寬。
用padding實現的單邊間隔
注意到寬度和上一種方式的不同了么?因為我們把box-sizing
屬性轉換成border-box
了。所以現在width
的計算包括了padding
值。
在這種情況下,三列中前兩列比最后一個要寬,這樣最終會導致奇怪的計算結果以及難以理解的CSS代碼。
我建議千萬不要嘗試這種方式。(如果你繼續這樣,代碼會變得非常丑陋。請在你風險控制范圍內嘗試!)
Method 3: 拆分間隔 (Margin)
用這種方式,你得把間隔分成2個並將它們分別置於在列的兩邊。代碼像這樣:
.grid-item {
/* Width property */
margin-right: 10px;
margin-left: 10px;
float: left;
}
然后,根據下面這張圖計算列寬:
用margin拆分間隔
根據之前我們了解到的,你需要用calc()
方法來計算列寬。對於現在這種情況,你要從100%的寬度里去掉三個間隔,再除以3就得到列寬了。換言之,列寬等於calc((100% - 20px * 3) / 3)
。
.grid-item {
width: calc((100% - 20px * 3) / 3);
margin-right: 10px;
margin-left: 10px;
float: left;
}
就這樣!(對於多行網格其實沒有其他額外需要做的 😉)。下面是代碼展示: 查看 用margins實現網格間隔拆分 by Zell Liew (@zellwk).
Method 4: 拆分間隔 (Padding)
這個和上一個類似。也是拆分間隔並分別放在列兩邊。但這次,是使用padding來替換間隔。
.grid-item {
/* width property */
padding-right: 10px;
padding-left: 10px;
float: left;
}
然后,再像下面這樣計算列寬:
通過padding拆分間隔
注意到這次計算列寬是不是簡單多了?這就對啦,每一列分界點剛好將網格分成三等份。
.grid-item {
width: 33.3333%;
padding-right: 10px;
padding-left: 10px;
float: left;
}
上代碼:
查看 padding實現的拆分間隔網格 by Zell Liew (@zellwk).
在我們繼續開始之前,如果你用padding分拆間隔的話給你一個小警告。仔細看一下Codepen上的標簽,你就會發現我在.grid-item里添加了一個額外的子元素,如果組件包含背景或者邊框的話這個是必須的。
這是因為背景會顯示在padding屬性上。下面這幅圖應該可以解釋為什么(希望可以),展示了background
和其他屬性間的關系。
背景顯示在padding上
我會用什么?
當我兩年前開始寫柵格時,我幾乎手寫柵格,通過自上而下的分析設計,內置一個混合系統。在那樣的系統/方法里,列寬和間隔值我都是使用的百分比。
在那時候,我喜歡單邊設置間隔的簡單。對我來說,這樣有較少的認知負荷,因為我實在不擅長數學。額外的間隔÷2
的計算就可以讓我迅速放棄。
我還是感激我繼續那樣做。盡管CSS看起來比拆分間隔更復雜,但我強迫自己學習恰當使用nth-child。我還知道了編寫 mobile-first CSS的重要性,據我所知,無論對於初學還是有經驗的開發者,這仍然是主要障礙。
然而,如果你現在讓我選擇,我會選擇拆分間隔而不是單邊間隔,因為這樣CSS會更簡單。同時,我更願意用margin而不是padding,因為這樣可以有更干凈的標簽。(但是padding更容易計算,所以在剩下的篇幅中我會繼續使用padding的方式。)
Step 6: 創建調試網格
當你開始時,有一個控制網格幫你調試布局會尤其管用。它可以幫助你確保你是在正確地創建東西。
在這一點上,我只知道一個蹩腳的創建調試網格的方式。就是創建HTML元素,再在上面添加樣式CSS。HTML如下:
<div class="fixed-gutter-grid">
<div class="column"></div>
<div class="column"></div>
<div class="column"></div>
<div class="column"></div>
<div class="column"></div>
<div class="column"></div>
<div class="column"></div>
<div class="column"></div>
<div class="column"></div>
<div class="column"></div>
<div class="column"></div>
<div class="column"></div>
</div>
針對調試網格的CSS如下(為了減少調試網格的標簽,我使用的是用margins拆分間隔):
.column {
width: calc((100% - 20px * 12) / 12);
height: 80px;
margin-right: 10px;
margin-left: 10px;
background: rgba(0, 0, 255, 0.25);
float: left;
}
查看代碼 Fixed gutter debug grid by Zell Liew (@zellwk) 。
(邊注:Miriam 和 Robson正打造一個Susy v3上的SVG-背景圖片調試網格。這令人超級雞凍啊,因為你可以用一個簡單的函數就能創建調試網格!)
Step 7: 創建可變的布局
下一步就是根據內容創建可變的布局。這就是CSS柵格系統閃光的地方。替代了通過寫重復的柵格類來創建布局,你可以根據內容創建合理的類名。
比如,假設你有一個只用於客戶文章的網格布局。桌面上布局看起來像這樣:
僅用於客戶文章的網格布局示例
標簽組織可以是這樣:
<div class="l-guest-article">
<div class="l-guest"> <!-- Guest profile --></div>
<div class="l-main"><!-- main article--></div>
<div class="l-sidebar"><!-- sidebar widgets--></div>
</div>
看起來很舒服。現在我們有12列。每一列的寬是8.333%(100 ÷ 12)
。
.l-guest
的寬度是兩列。所以,就是8.333% 乘2,就這么簡單。以此類推。
這里,我建議使用Sass這樣的預處理器,這樣可以輕松地用percentage
函數計算列寬,就不用手動計算了:
.l-guest-article {
@include clearfix;
.l-guest {
// Ahem. More readable than 16.666% :)
width: percentage(2/12);
padding-left: 10px;
padding-right: 10px;
float: left;
}
.l-main {
width: percentage(7/12);
padding-right: 10px;
padding-left: 10px;
float: left;
}
.l-sidebar {
width: percentage(3/12);
padding-right: 10px;
padding-left: 10px;
float: left;
}
}
查看代碼 Content-sidebar-layout with fixed-gutter grid by Zell Liew (@zellwk) .
你可能已經發現到現在有很多重復的代碼。我們可以通過抽出公共的部分到一個單獨的選擇器比如.grid-item
里來優化它。
.grid-item {
padding-left: 10px;
padding-right: 10px;
float: left;
}
.l-guest-article {
.l-guest { width: percentage(2/12);}
.l-main { width: percentage(7/12);}
.l-sidebar { width: percentage(3/12); }
}
干凈多了 :)
Step 8: 使布局響應
最后一步就是讓布局可響應。假設我們的客戶文章布局按照下面的方式響應:
對於不同視口文章布局如何響應
我們標簽不用變。我們現在已經有了可能是最易理解的布局。所以,要改變的完全應該是CSS。
當寫響應式布局的CSS時,我強烈建議你寫mobile first css,因為它能讓你的代碼更簡單優雅。我們可以開始優先對手機端布局寫CSS。 上代碼:
.l-guest-article {
.l-guest { /* nothing goes here */ }
.l-main {
margin-top: 20px;
}
.l-sidebar {
margin-top: 20px;
}
}
我們不需要再做什么,因為每個組件默認是占滿寬度。然而,我們可以添加一些上邊距到最后兩項上,從而使元素相互分開。
接下來,我們移動到平板端的布局。
對於這個,假設在分界點是700px時觸發。.l-guest
應該是4列(一共12列), .l-main
和.l-sidebar
每個應該是8列。
這里,我需要去掉.l-main
的margin-top
屬性,因為它需要和.l-guest
排成一行。
而且,如果我們設置.l-sidebar
為8列的寬度,那它會自動浮動到第二行,因為第一行沒有足夠的空間可以容納它。既然顯示在第二行了,我們也需要在.l-sidebar
上添加一些左邊距來將它放到合適的位置;要不然,我們將它浮動在右邊。(我還是把它浮動在右邊吧,這樣不需要什么計算)。
最后,一旦我們浮動這些網格項,網格容器就需要一個clearfix來清除它們。
.l-guest-article {
@include clearfix;
.l-guest {
@media (min-width: 700px) {
width: percentage(4/12);
float: left;
}
}
.l-main {
margin-top: 20px;
@media (min-width: 700px) {
width: percentage(8/12);
margin-top: 0;
float: left;
}
}
.l-sidebar {
margin-top: 20px;
@media (min-width: 700px) {
width: percentage(8/12);
float: right;
}
}
}
最后一點,讓我們來看看桌面布局。
對這種布局,我們假設觸發的分界點是1200px。.l-guest
應該是總寬的2/12,.l-main
應該是7/12,.l-sidebar
是3/12.
我們要做的就是在每個網格項上創建一個新的媒體查詢,再根據需要改變寬度。得注意也需要去掉',l-sidebar
上的margin-top屬性。
.l-guest-article {
@include clearfix;
.l-guest {
@media (min-width: 700px) {
width: percentage(4/12);
float: left;
}
@media (min-width: 1200px) {
width: percentage(2/12);
}
}
.l-main {
margin-top: 20px;
@media (min-width: 700px) {
width: percentage(8/12);
margin-top: 0;
float: left;
}
@media (min-width: 1200px) {
width: percentage(7/12);
}
}
.l-sidebar {
margin-top: 20px;
@media (min-width: 700px) {
width: percentage(8/12);
float: right;
}
@media (min-width: 1200px) {
width: percentage(3/12);
margin-top: 0;
}
}
}
最終布局的代碼: 查看guest-article layout with fixed-gutter grid (final) by Zell Liew (@zellwk).
(噢,順便說一下,你也可以用Susy實現這些效果。只要記住設置 間隔位置為inside-static
)
總結
哇哦!文章是挺長的。寫這篇文章我也是寫到死了幾次了。(感謝你能從頭讀到尾。我希望你看下來沒有吐血)(ps:其實翻譯的有點快吐血了)
正如你看到的,創建一個響應式柵格系統的步驟是相對簡單直觀的。人們最容易混亂的部分是第五步(決定間隔位置)以及第8步(使布局可響應)。