# margin系列之布局篇
>原作者:doyoe
原文鏈接:http://blog.doyoe.com/2013/12/31/css/margin%E7%B3%BB%E5%88%97%E4%B9%8B%E5%B8%83%E5%B1%80%E7%AF%87/
## 前端工程師對CSS的基本訴求
布局能力或許是Web前端工程師對CSS的最基本的訴求,當開始進入到這個崗位,就避免不了要和CSS打交道,而和CSS交往,布局當然是不可或缺的。
很遺憾的是,CSS2.1之前都沒有出現真正意義上的布局屬性,直至現如今的CSS3,才開始出現了一些,如:flex, grid 等,不過其兼容性及國內瀏覽器的使用情況,真令人捉急。
不過,有需求就會有變通,對于達成布局目的,已衍生出各式各樣的方法,如:float, inline-block, table, absolute 等等。
## margin的布局之道
其實,這個話題有點脫離 `margin` 的能力范圍,因為單純的 `margin` 并無法完成復雜布局,它更多做的是輔助,但卻又難以替代。
## 經典左右結構
兩欄結構應該是最常見和經典的網頁呈現之一吧?如下 `圖一`:
(圖一)
相信對于這樣一個網頁呈現,你不會陌生。那么你有多少種方案可以達成該布局?我想,4至5種應該是保守估計吧?
這次,我們只看 `margin` 是如何做的。
## absolute + margin 方式
### HTML
```html
<header id="hd">頭部</header>
<div id="bd">
<aside id="aside">側邊欄固定寬度</aside>
<div id="main">主內容欄自適應寬度</div>
</div>
<footer id="ft">底部</footer>
```
### CSS
```css
#aside{
position:absolute;
top:0;
left:0;
width:200px;
}
#main{
margin-left:210px;
}
```
如上關鍵代碼,我們即可實現 `圖一` 布局,該布局有一個特點就是,`#main` 可以自適應可用空間。
假定 `HTML` 是給定的,我們來解讀一下 `CSS` 代碼:
我們知道塊級元素的特性之一是換新行,也就是說,如果想讓 `#main` 和 `#aside` 在同行顯示,我們要么改變其顯示屬性為 `inline-level`(即之前說的inline-block布局方式),要么改變其流方式(absolute, float, flex and etc…)。
如上述代碼,我們使用了 `absolute`,即讓 ‘#aside’ 脫離常規流,通過絕對定位到想要的位置。
### 主內容欄自適應寬度
同時你會發現,我們并有改變 `#main` 的顯示屬性或者流方式,也就是說其仍然具備塊級元素的特性,所以它會自動適應剩余寬度,即我們常說的自適應寬度。
我們并不希望 `#main` 區域會包含 `#aside` 在內,于是利用 `margin` 給 ‘#aside’ 預留出足夠其顯示的空間,即可達成我們所要的布局。
可能你會問為什么是 `margin-left:210px` 而不是 `200px`,實際確實應該是 `200px`,多出來的 `10px` 只是為了創建一個列間隙,與布局實現無關。
來看看具體的實現 `DEMO1`: [margin+absolute布局:左欄固定主內容自適應](http://demo.doyoe.com/css/margin/layout/absolute-margin.html)
就這樣,是不是很簡單?其實它還有亮點,那就是:
### 任意調整列順序
在不修改 HTML 的情況下,只需簡單的修改 CSS,我們即可讓左右兩欄的順序調換,來看代碼:
### CSS
```css
#aside{
position:absolute;
top:0;
right:0;
width:200px;
}
#main{
margin-right:210px;
}
```
其實現原理沒變,同樣看看 `DEMO2`: [margin+absolute布局:右欄固定主內容自適應](http://demo.doyoe.com/css/margin/layout/absolute-margin-2.html)
### 主內容優先顯示
可以更Cool一點,你覺得呢?很多時候,你也許會考慮到,不論在何種情況下,總想保證主要的內容優先于次要的內容呈現給用戶,那么,怎么做?
很簡單,只需要將主要內容的HTML排在次要內容的HTML之前即可,因為它是順序加載渲染的。我們可以這樣:
### HTML
```html
<header id="hd">頭部</header>
<div id="bd">
<div id="main">主內容欄自適應寬度</div>
<aside id="aside">側邊欄固定寬度</aside>
</div>
<footer id="ft">底部</footer>
```
是的,我們只需要將 `#main` 的HTML挪到 `#aside` 的HTML前面,令人興奮的是,改變HTML之后,CSS不需要做任何改變。我們來看 `DEMO3`: [margin+absolute布局:左欄固定主內容自適應,主內容有限顯示](http://demo.doyoe.com/css/margin/layout/absolute-margin-3.html)
當然,調正列順序的 `DEMO4`: [margin+absolute布局:右欄固定主內容自適應,主內容有限顯示](http://demo.doyoe.com/css/margin/layout/absolute-margin-4.html) 也同樣簡單,我們只需要寫HTML時注意一下即可。
### 致命缺陷
列舉了 `absolute+margin` 布局的很多優點,但只說一個問題,就足以讓你在是否選用這種方式時深思熟慮,是什么呢?
我們知道 `absolute` 是定位流,脫離正常排版,也就是說絕對定位元素不影響其上下文的排版方式,你意識到我想說什么了么?
OK,用代碼來演示:
### HTML
```html
<header id="hd">頭部</header>
<div id="bd">
<div id="main">主內容欄自適應寬度</div>
<aside id="aside">側邊欄固定寬度,我的內容可能比主內容多,高度比主內容欄高</aside>
</div>
<footer id="ft">底部</footer>
```
看完代碼,估計你猜到了。是的,`#aside` 無法撐開父元素的高度,它將會溢出父元素區域,結果如下圖:
(圖二)
來看看這缺陷所導致的情況 `DEMO5`: [margin+absolute布局的致命缺陷](http://demo.doyoe.com/css/margin/layout/absolute-margin-5.html)
此時假設你設置父元素 `overflow:hidden` 那么溢出部分將會被裁減,同樣不符合布局意圖,無法可破。所以在內容量不可控的場景,不推薦使用這種方式。
## float + margin 方式
和 `absolute + margin` 方式一樣,`float + margin` 方式一樣是經典的利用來布局的方案,并且被更廣泛使用。我們仍然以 `圖一` 為例,來看代碼:
### HTML
```html
<header id="hd">頭部</header>
<div id="bd">
<aside id="aside">側邊欄固定寬度</aside>
<div id="main">主內容欄自適應寬度</div>
</div>
<footer id="ft">底部</footer>
```
### CSS
```css
#aside{
float:left;
width:200px;
}
#main{
margin-left:210px;
}
```
如上述代碼,我們使用了 `float`,即從圖文環繞形態演變而來。當 `#aside` 定義了 `float`,那么緊隨其后的元素將會環繞在其周圍。不過環繞并不是我們想要的結果,我們想要的是 ‘#main’ 也自成封閉矩形,所以利用 `margin` 留出足夠 `#aside` 顯示的空間,中斷環繞即可。
當然,此時 `#main` 也是自適應寬度的,來看具體實例 `DEMO6`: [margin+float布局:左欄固定主內容自適應](http://demo.doyoe.com/css/margin/layout/float-margin.html)
它是否也具備可任意調整列順序的特點?何不一試?
### CSS
```css
#aside{
float:right;
width:200px;
}
#main{
margin-right:210px;
}
```
看過 `DEMO7`: [margin+float布局:右欄固定主內容自適應](http://demo.doyoe.com/css/margin/layout/float-margin-2.html),你會發現,是的,這種方式也支持任意調整列順序,很棒。
從這種趨勢看來,貌似 `float + margin` 的方式會成為黑馬,不過遺憾的告訴你,這種方式無法支持主內容優先顯示。但我們有更Cool的解決方案。
## float + 負margin 方式
接下來我要說的大家可能都猜到了,對,經典的圣杯布局。至于圣杯的名字由來,大家可以自行Google,這里不做贅述。
恩,HTML當然是使用主內容優先顯示的那種:
### HTML
```
<header id="hd">頭部</header>
<div id="bd">
<div id="main">主內容欄自適應寬度</div>
<aside id="aside">側邊欄固定寬度</aside>
</div>
<footer id="ft">底部</footer>
```
### CSS
```css
#bd{
padding-left:210px;
}
#aside{
float:left;
position:relative;
left:-210px;
width:200px;
margin-left:-100%;
}
#main{
float:left;
width:100%;
}
```
如上代碼,既是圣杯布局的核心Code,如果你看懂了,你會發現,這其實很簡單,不是么?
簡單解釋一下上面的CSS Code,首先我們是在做一個左側固定寬度,右側自適應寬度的布局。我們說過要讓塊級元素在同行顯示的條件:改變顯示方式,改變流方式,這里我們選擇了使用 `float` 來將 `#main` 和 `#aside` 變成浮動流。
OK,這時我們具備 `#main` 和 `#aside` 能在同行顯示的前置條件。我們知道,浮動元素其寬度如果沒有顯式定義,則由其內容決定。正好,`#aside` 是定寬的,所以顯示給它定義 `width:200px`,但此時 `#main` 該怎么辦?不設置 `width` 不對,因為寬度將被內容左右,設置 `width:100%` 也不對,因為這樣的話,就沒有 `#aside` 的立足之地了,正確的應該是 `width: calc(100% - 200px)`,不是么?可惜,這是新特性,只好作罷。
變通?是的,有的時候稍微換個思路,你會覺得豁然開朗。
`#main` 不是要自適應嗎?那就給它個 `100%`,怎么做?我們在包含塊 `#bd` 中就將 `#aside` 的寬度刨除,寬度全部都給 `#main`。恩,我們只需要這樣 `#bd{padding-left:210px;}` (10px仍然是用來做間隙的),這時 `#main` 就可以設置 `width:100%` 了,由于 `#bd` 設置了 `padding`,所以已在左邊預留出了一塊寬 `210px` 的區域。此時的問題在于如果將 `#aside` 挪到這個地方,你想對了,我們是在聊 負margin 布局,自然需要利用上。
`#aside{margin-left:-100%;}` 這樣可以了嗎?很明顯,這樣還不行,此時 `#aside` 和 `#main` 的起始位置將會重合,因為 `#aside` 的 `margin-left` 計算值是相對包含塊來計算的,而此時包含塊的寬度等于 `#main` 的寬度。
如何讓 `#aside` 再向左偏移 `210px`?顯然 `margin` 是不行了,因為我們已經用掉它了。如果你看過之前的文章的話,你可能還記得,有一篇文章講 [margin系列之與相對偏移的異同](http://www.hmoore.net/jaya1992/fe-notes/81816)。恩,是的,這時我們可以借助相對偏移。
向左偏移 `210px` 是件很簡單的事:`#aside{position:relative;left:-210px;}`。
至此,你的布局OK了,這就是圣杯的實現方式。來看已實現好的示例 `DEMO8`: [圣杯:左欄固定主內容自適應](http://demo.doyoe.com/css/margin/layout/holy-grail.html)
當然,圣杯布局必須可以任意調整列順序,要不,怎么能說是更Cool些的方案呢?
### CSS
```css
#bd{
padding-right:210px;
}
#aside{
float:left;
position:relative;
right:-210px;
width:200px;
margin-left:-200px;
}
#main{
float:left;
width:100%;
}
```
這個就直接看示例好了,不再一一解釋代碼 `DEMO9`: [圣杯:右欄固定主內容自適應](http://demo.doyoe.com/css/margin/layout/holy-grail-2.html)
所以圣杯布局具備前兩種方式共同的優點,同時沒有他們的不足,但圣杯本身也有一些問題,在IE6/7下報廢,不過不用慌,因為它可被修復。
你想到方法了嗎?