布局的基本概念
多欄布局有三種基本的實現(xiàn)方案:固定寬度、流動、彈性。
- 固定寬度布局的大小不會隨用戶調(diào)整瀏覽器窗口大小而變化,一般是900 到1100像素寬。其中960 像素是最常見的,因為這個寬度適合所有現(xiàn)代顯示器,而且能夠被16、12、10、8、6、5、4 和3 整除,不僅容易計算等寬分欄的數(shù)量,而且計算結果也能得到?jīng)]有小數(shù)的像素數(shù)。流行的CSS 布局框架960 Grid(http://www.960.gs),就是基于960 像素寬的網(wǎng)格創(chuàng)建的。
- 流動布局的大小會隨用戶調(diào)整瀏覽器窗口大小而變化。這種布局能夠更好地適應大屏幕,但同時也意味著放棄對頁面某些方面的控制,比如隨著頁面寬度變化,文本行的長度和頁面元素之間的位置關系都可能變化。Amazon.com 的頁面采用的就是流動中欄布局,在各欄寬度加大時通過為內(nèi)容元素周圍添加空白來保持內(nèi)容居中,而且現(xiàn)在的導航條會在布局變窄到某個寬度時收縮進一個下拉菜單中,從而為內(nèi)容騰出空間。今天,越來越多的瀏覽器都支持CSS 媒體查詢了。這就讓基于瀏覽器窗口寬度提供不同的CSS 樣式成為可能。在這種形勢下,適應各種屏幕寬度的可變固定布局,正逐步取代流動布局。這種可變的固定布局能夠適應最大和最小的屏幕,業(yè)界稱之為響應式設計。
- 彈性布局與流動布局類似,在瀏覽器窗口變寬時,不僅布局變寬,而且所有內(nèi)容元素的大小也會變化,讓人產(chǎn)生一種所有東西都變大了的感覺。到目前為止,我還沒見過設計得非常好的彈性布局,主要是因為它太過復雜了。
布局高度與布局寬度
在實際地創(chuàng)建頁面布局之前,我想先說說應該怎么看待布局的高度和寬度,因為這兩者的控制方法實在太不一樣了。
布局高度
多數(shù)情況下,布局中結構化元素(乃至任何元素)的高度是不必設定的。事實上,我甚至想告訴你根本不應該給元素設定高度。除非你確實需要這樣做,比如在頁面中創(chuàng)造一個絕對定位的元素。
為什么正常情況下都應該保持元素height 屬性的默認值auto 不變呢?很簡單,只有這樣元素才能隨自己包含內(nèi)容的增加而在垂直方向上擴展。這樣擴展的元素會把下方的元素向下推,而布局也能隨著內(nèi)容數(shù)量的增減而垂直伸縮。假如你明確設定了元素的高度,那么超出的內(nèi)容要么被剪掉,要么會跑到容器之外——取決于元素overflow 屬性的設定。
布局寬度
與高度不同,我們需要更精細地控制布局寬度,以便隨著瀏覽器窗口寬度的合理變化,布局能夠作出適當?shù)恼{(diào)整,確保文本行不會過長或過短。如果隨意給元素添加內(nèi)邊距、邊框,或者元素本身過大,導致浮動元素的寬度超過包含元素的布局寬度,那浮動元素就可能“躲”到其他元素下方。
當然啦,即使必須設定欄寬,也不要給包含在其中的內(nèi)容元素設定寬度,應該讓這些內(nèi)容元素自動擴展到填滿欄的寬度。本書前面已經(jīng)講過了,這是塊級元素的默認行為。簡言之,就是讓欄寬限制其中內(nèi)容元素的寬度。
三欄-固定寬度布局
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html;charset=UTF-8" />
<title>HTML5 Template</title>
<!-- <link rel="stylesheet" href="./css/test.css"> -->
<style>
* {
margin: 0;
padding: 0;
}
#wrapper {
width: 960px;
margin: 0 auto;
border: 1px solid;
}
header {
background: #f00;
}
nav {
width: 150px;
float: left;
background: #dcd9c0;
}
nav li {
list-style-type: none;
}
article {
width: 600px;
float: left;
background: #ffed53;
}
aside {
width: 210px;
float: left;
background: #3f7ccf;
}
footer {
clear: both;
background: #000;
}
</style>
</head>
<body>
<div id="wrapper">
<header>
標題
</header>
<nav>
<ul>
<li>無序列表1</li>
<li>無序列表2</li>
<li>無序列表3</li>
</ul>
</nav>
<article>
如圖5-2 所示,把兩欄容器元素的總寬度設定為外包裝的寬度(150 + 810 = 960), 并浮動它們,就可以創(chuàng)造出并肩排列的兩欄來。每一欄的長度取決于內(nèi)容多少。采 用同樣的方法,可以添加第三欄(或任意多個欄)。
</article>
<aside>
文本
</aside>
<footer>
footer
</footer>
</div>
</body>
</html>

為欄設定內(nèi)邊距和邊框
只要一調(diào)整各欄中的內(nèi)容,布局就可能超過容器寬度,而右邊的欄就可能滑到左邊的欄下方。一般來說,兩種情況下可能會發(fā)生這種問題。
- 為了讓內(nèi)容與欄邊界空開距離,為欄添加水平外邊距和內(nèi)邊距,或者為了增加欄間距,為欄添加外邊距(只要開始給布局添加樣式,就一定會采用這里說的一種做法,甚至雙管齊下),導致布局寬度增大,進而浮動欄下滑。換句話說,右邊浮動的欄因為沒有足夠的空間與其他欄并列,就會滑到左邊欄的下方。
- 在欄中添加大圖片,或者沒有空格的長字符串(如長URL),也會導致欄寬超過布局寬度。同樣,這種情況下右邊的欄也會滑到左邊的欄下方。
為固定寬度的元素添加水平外邊距、邊框和內(nèi)邊距,會導致元素盒子變寬
好在,我們也有三種方法來預防該問題發(fā)生。
- 從設定的元素寬度中減去添加的水平外邊距、邊框和內(nèi)邊距的寬度和。
- 在容器內(nèi)部的元素上添加內(nèi)邊距或外邊距。
- 使用CSS3 的box-sizing 屬性切換盒子縮放方式,比如section {box-sizing:border-box;}。 應用box-sizing 屬性后,給section 添加邊框和內(nèi)邊距都不會增大盒子,相反會導致內(nèi)容變窄。
直接給欄應用內(nèi)邊距會導致內(nèi)容變窄,但不會影響布局。聽起來容易
的辦法總會有一個“但是”,這里的“但是”要說的是IE6 和IE7 不支持box-sizing
屬性。不過,有一個專門解決這個問題的膩子腳本(polyfill),名叫borderBoxModel.js。
你可以使用條件注釋(以便只有IE6 和IE7 加載)把它添加到HTML 標記之后、結
束的</body>標簽之前,以保證在加載DOM 之后再執(zhí)行該腳本:
<body>
<!-- HTML 標記 -->
<!-- 只讓IE8 之前的IE 加載它 -->
<!--[if lt IE 8 ]>
<script src="helpers/borderBoxModel.js"></script>
<![endif]-->
</body>
然而,一欄之中可能會包含大量不同內(nèi)容的元素。假如將來又決定調(diào)整內(nèi)容與容器邊界的距離,就必須每個元素都要進行調(diào)整,這樣不僅麻煩,而且容易出錯。況且,給欄添加邊框同樣會增大欄寬,不可能通過為其包含的內(nèi)容元素逐個應用樣式來做到。
所以說,與其為容器中的元素添加外邊距,不如在欄中再添加一個沒有寬度的div,讓它包含所有內(nèi)容元素,然后再給這個div 應用邊框和內(nèi)邊距。如此一來,只要為內(nèi)部div 設定一次樣式,就可以把讓所有內(nèi)容元素與欄邊界保持一致的距離。而且,將來再需要調(diào)整時也會很方便。任何新增內(nèi)容元素的寬度都由這個內(nèi)部div 決定。
關于表現(xiàn)性標記的思考
HTML 的目的是語義,也就是給內(nèi)容賦予含義。而CSS 呢,是為了把表現(xiàn)性的樣式分離出來才發(fā)明的。不過,有些表現(xiàn)性標記是有害的,而有些則沒有副作用。使用表格來創(chuàng)建多欄布局,或者使用<br />標簽在段間換行,卻不使用<p>標簽,這種做法的確不值得提倡,因為這會造成內(nèi)容難以移植。比如說吧,用三個表格單元作為三欄,這種布局到哪都會顯示成表格,就算是在完全不合適的智能手機里也一樣。如果表現(xiàn)性標記無法用CSS 修改,或者在CSS 不可用時也要迫使用戶接受,那就是濫用HTML。可是,div 或span 這種中性的元素,對默認樣式?jīng)]有影響,除非你給它們應用樣式,否則它們就跟不存在一樣。所以,我認為添加這種元素達到表現(xiàn)性的目的是完全可以接受的。
子-星選擇符
所謂“子-星選擇符”就是一個組合選擇符,利用它可以不使用內(nèi)部div 就能設定一欄中所有元素的外邊距。
星號選擇符可以選擇“所有元素”,故而,在一個選擇符后面加個星號,比如someSelector *就可以選擇someSelector 所代表元素的所有后代元素。子選擇符可以選擇“某元素的子元素”,故而,把子選擇符放到星號前面,比如someSelector > *就會只選擇someSelector所代表元素的所有子元素,而非后代元素。這正好適用于選擇容器內(nèi)部的所有頂部元素,然后設定它們的外邊距。比如,對于section 欄,設定section > * {margin:0 10px;},就能為欄中所有子元素,不包括其他后代元素,各應用10 像素的左、右外邊距。 使用“子-星選擇符”要注意兩點。
第一,在為子元素設定垂直外邊距時,只能使用margin-top 和margin-bottom,不能使用簡寫的margin,否則會抵消用“子-星選擇符”應用給這些元素的水平外邊距。如果你想進一步縮進某個子元素的內(nèi)容,就應該給該子元素應用內(nèi)邊距。
第二,“子-星選擇符”有潛在性能問題,因為它會導致瀏覽器遍歷整個DOM 結構去查找所有匹配的元素。但我也發(fā)現(xiàn)這一點性能影響幾乎可以忽略不計。假如你的頁面真的包含幾千上萬個元素,那倒確實該考慮用ySlow 或其他性能度量工具測一測這個選擇符的影響。
預防過大的元素
設計一個將來可能由他人維護的動態(tài)網(wǎng)站時,需要考慮得更長遠一些。比如,應該預見到可能出現(xiàn)一些過大的元素。如果將來有一張比浮動欄更寬的圖片被放到欄中,就會導致布局變寬,而右邊的欄又會滑到下方。為此, 一個簡單的預防措施就是添加一條.inner img{max-width:100%;}聲明,以便限制圖片的寬度不超過其父元素(在此就是內(nèi)部div)。
另一個辦法是給每個欄(或者內(nèi)部div,如果你用了的話)添加overflow:hidden 聲明。這條聲明不會縮小圖片以適應父元素,而會將它(以及任何過大元素)超出容器邊界的部分剪切掉。動態(tài)網(wǎng)站中另一個潛在的問題是換行。HTML 只會在單詞間空格的地方換行。一些長URL,甚至一些長單詞,在欄比較窄的情況下,都會導致欄寬過大。因此,還應該給所有欄的外包裝元素應用word-wrap:break-word 聲明,以便所有欄及其內(nèi)容繼承這個設定。有了這條聲明,瀏覽器會把過長的詞斷開顯示在不同行上。只是word-wrap 沒有定義在哪里斷開,因此結果完全是隨機的,而且沒有連字符。不過,這條規(guī)則只在需要時才會起作用,而且能保護布局不會被長URL 頂?shù)弥щx破碎。建議你在每一欄中都用長URL、大圖片,以及包含內(nèi)容過多的元素測試一下布局,看看這些聲明到底會不會起作用,并發(fā)現(xiàn)更多需要事先考慮保護措施的其他漏洞。
三欄-中欄流動布局
實現(xiàn)中欄流動布局有兩種方法。一種是在中欄改變大小時使用負外邊距定位右欄,另一種是使用CSS3 讓欄容器具有類似表格單元的行為。負外邊距適合比較老的瀏覽器,而CSS 的table 屬性則要簡單得多。
用負外邊距實現(xiàn)
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html;charset=UTF-8" />
<title>HTML5 Template</title>
<style>
* {
margin: 0;
padding: 0;
}
body {
font: 1em helvetica, arial, sans-serif;
}
div#main_wrapper {
min-width: 600px;
max-width: 1100px;
/*超過最大寬度時,居中布局*/
margin: 0 auto;
/*背景圖片默認從左上角開始拼接*/
background: url(images/bg_tile_150pxw.png) repeat-y #eee;
}
header {
padding: 5px 10px;
background: #3f7ccf;
}
div#threecolwrap {
/*浮動強制它包圍浮動的欄*/
float: left;
width: 100%;
/*背景圖片右對齊*/
background: url(images/bg_tile_210pxw.png) top right repeat-y;
}
div#twocolwrap {
/*浮動強制它包圍浮動的欄*/
float: left;
width: 100%;
/*把右欄拉到區(qū)塊外邊距騰出的位置上*/
margin-right: -210px;
}
nav {
float: left;
width: 150px;
background: #f00;
padding: 20px 0;
}
/*讓子元素與欄邊界保持一定距離*/
nav>* {
margin: 0 10px;
}
article {
width: auto;
margin-left: 150px;
/*在流動居中的欄右側騰出空間*/
margin-right: 210px;
background: #eee;
padding: 20px 0;
}
/*讓子元素與欄邊界保持一定距離*/
article>* {
margin: 0 20px;
}
aside {
float: left;
width: 210px;
background: #ffed53;
padding: 20px 0;
}
/*讓子元素與欄邊界保持一定距離*/
aside>* {
margin: 0 10px;
}
footer {
clear: both;
width: 100%;
text-align: center;
background: #000;
}
</style>
</head>
<body>
<div id="main_wrapper">
<header>
<!-- 頁眉-->
</header>
<!-- /*三欄外包裝(包圍全部三欄)*/ -->
<div id="threecolwrap">
<!-- /*兩欄外包裝(包圍左欄和中欄)*/ /*左欄*/ -->
<div id="twocolwrap">
<nav>
<!-- 導航 -->
</nav>
<!-- /*中欄*/ -->
<article>
<!-- 區(qū)塊 -->
</article>
<!-- /*結束兩欄外包裝(twocolwrap)*/ /*右欄*/ -->
</div>
<aside>
<!-- 側欄 -->
</aside>
<!-- /*結束三欄外包裝(threecolwrap)*/ -->
</div>
<footer>
<!-- 頁腳 -->
</footer>
</div>
</body>
</html>

用CSS3 單元格實現(xiàn)
盡管利用HTML 的<table>標簽實現(xiàn)多欄布局是難以接受的,但使用CSS 讓布局形如表格則是絕對可以接受的。這種方法不會導致固定不變的表格布局,也不會出現(xiàn)難以重新應用樣式的問題(比如在手持設備上表現(xiàn)為一欄)。
我們知道,CSS 可以把一個HTML 元素的display 屬性設定為table、table-row 和table-cell。通過這種方法可以模擬相應HTML 元素的行為。而通過CSS 把布局中的欄設定為table-cell 有三個好處。
- 單元格(table-cell)不需要浮動就可以并排顯示,而且直接為它們應用內(nèi)邊距也不會破壞布局。
- 默認情況下,一行中的所有單元格高度相同,因而也不需要人造的等高欄效果了。
- 任何沒有明確設定寬度的欄都是流動的。
CSS3 表格行為在IE7 及更低版本中并沒有得到支持,而且也沒有穩(wěn)妥的補救措施。如果你(或者你的客戶)愿意摒棄IE7,那么它就是一個既簡單又可靠,而且還很徹底的解決方案。
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html;charset=UTF-8" />
<title>HTML5 Template</title>
<style>
nav {
display: table-cell;
width: 150px;
padding: 10px;
background: #dcd9c0;
}
article {
display: table-cell;
padding: 10px 20px;
background: #ffed53;
}
aside {
display: table-cell;
width: 210px;
padding: 10px;
background: #3f7ccf;
}
</style>
</head>
<body>
<nav>
<!-- 內(nèi)容 -->
</nav>
<article>
<!-- 內(nèi)容 -->
請注意,這個簡單、功能完備的布局對IE7 和IE6 可沒有任何膩子腳本,甚至連個退
化的后備方案都沒有。在這些瀏覽器中,三欄會上下堆疊在一起。因此,除非你下
定決心不再支持老版本的IE,否則就得使用本章前面講過的其他布局技術。等吧,
等到這些瀏覽器沒人用為止。
</article>
<aside>
<!-- 內(nèi)容 -->
</aside>
</body>
</html>

多行多欄布局
CSS選擇符的實際應用
隨著頁面變得越來越復雜,相同的HTML 元素(如section、article、nav,等等)會出現(xiàn)很多次——比如,前面布局中的article 就出現(xiàn)了7 次。為了選擇某個元素,必須區(qū)分這些相同的標簽名。為此,有些新手會給每個標簽都添加一個不同的類名。但這種做法是不值得提倡的。不僅因為類本身就不該這么用(類應該用于標記具有相同特征的元素),而且這么多類會把標記弄得很亂,讓CSS 也很難看懂。為了知道每個類代表哪個元素,你必須不斷地查看HTML。
更好的做法是給標記中每個主要區(qū)域的頂級元素添加一個ID,這也是使用ID 的正確方式,ID 就是標識頁面中唯一元素用的。然后,這些ID 就會成為HTML 標記中的
“路標”,放在上下文選擇符開頭的時候,它們就能起到框定后代元素的作用。這就是在標記中保持類和ID 屬性最少的秘訣。而且,相應的上下文選擇符也能清晰地傳達出路徑信息,讓人從CSS 中一眼就能看出它要選擇哪個元素。
<div id="wrapper">
<header>
<h1>Full-width content</h1>
</header>
<nav>
<p>Navigation menus go here</p>
</nav>
<section id="branding">
<img src="images/grand_canyon.jpg" alt="Grand Canyon" />
</section>
<!-- branding 結束 -->
<section id="feature_area">
<article>
<div class="inner">
<p>Lorem Ipsum text</p>
</div>
</article>
<!-- 省略另外兩個 article 元素 -->
</section>
<!-- feature_area 結束-->
<section id="promo_area">
<article>
<div class="inner">
<p>Lorem Ipsum text</p>
</div>
</article>
<!-- 省略另外三個 article 元素 -->
</section>
<!-- promo_area 結束-->
<footer>
</footer>
</div>