如何實現一個三欄自適應布局,左右各100px,中間隨着瀏覽器寬度自適應?
第一個想到的是使用table布局,設置table的寬度為100%,三個td,第1個和第3個固定寬度為100px,那么中間那個就會自適應了,下面是一個實時的demo:
left | middle | right |
但是table布局是不推薦的,table布局是css流行之前使用的布局,有很多缺點:當table加載完之前,整個table的都是空白的,table將數據和排版參和在一起,使得頁面混亂,並且table布局修改排版十分麻煩和困難。
如果不用table布局,那么第二個想到的辦法是采用float,讓左邊的div float left,右邊的div float right,如下邊所示:
Action 1 先讓左右兩個div浮動
中間還有一個div,如果將中間的div排在第二:
<div style="float:left;">left</div> <div>middle</div> <div style="float:right;">right</div>
那么效果是這樣的:
Action 2 右邊的div浮動到了第二行
因為div默認的display為block,如果不設置width的話,塊級元素會盡可能多地占用水平空間。如果設置了width: 200px,效果是這樣的:
Action 3 右邊的div仍然浮動到了第二行
第三個div仍然會換行,因為float right會排到當前行盡可能右邊的位置,即它的容器盒的邊緣或者挨着的上一個float的元素,如果當前行沒有空間的話,會不斷地往下移,直到有足夠的空間。由於middle是一個塊盒,即使設置了width,當前行的空間也會被占用,所以right只能到下一行才有空間。
同時注意到middle雖然設置了200px,但是看起來和left一樣是100px寬了。這是因為float了的元素雖然在正常的文檔流之內,但是只是讓相鄰(非float)的元素的內容圍繞着它排列,它仍然占據相鄰元素的background和border空間。如果給middle添加一個白色的border,那么看起來是這樣的:
Action 4 浮動的元素占據了文檔流相應元素的背景和邊緣空間
明顯看到,float left的元素占據了middle的background和border的空間,同時middle的內容圍繞着left排列。理解這點很重要。
假設middle里面有個p標簽,而p標簽的內容比較長,那么圍繞的效果是這樣的:
Action 5 浮動的環繞效果
環繞的元素一旦超出float元素高度之后,會以正常的寬度顯示
正如上面的注釋一樣,在float元素的那一行,相鄰的元素的內容的寬度將會縮短,以適應float元素占去的寬度,而一旦超過float元素的區域,相鄰元素的內容顯示寬度就會正常。
由於默認的div會占一行,所以不能將middle放在第二個div,得放到第三個div。把第二個div和第三個div換一下順序:
<div style="float:left;">left</div> <div style="float:right;">right</div> <div>middle</div>
先讓float right的div渲染,再渲染middle的div。因為渲染left之后,left的那一行仍然有空間,這是由於float left之后,只會占據當前行的background和border,而當前行還有很大的空間,於是第二步渲染right時就和left同一行了,效果:
Action 6 先渲染左右兩個div,再渲染中間的div
如果不設置middle的width,那么middle將圍繞着left和right環繞,和left一樣,right也會占用middle的空間。
Action 7 中間的div圍繞着左右的div環繞
為了讓middle和left/right中間有一個margin值,設置下middle的左右margin各為110px,這樣就和左右和中間就各有10px的間距:
Action 8 設置中間div的margin值為100px + 10px
這種辦法的優點是實現簡單,支持性好。
這種自適應寬度的原理是利用了float的圍繞特性,占據自然文檔流的background/border位置。這個圍繞特性不僅會影響當前行的內容,還會影響下一行的內容,如下說明:
<p>第一段內容,略<img src="" style="float:left;"></img></p> <p>第二段內容,略</p>
Action 8 float元素占據了下一行的空間
第一段落圍繞着圖片排列
圖片的float屬性也影響了第二段落,也就是說float會占據自然文本流相應位置元素的背景和邊框,即使和float的元素不在同一行
網上還有一種margin負值法。margin負值法的步驟是:第一步讓中間的middle占100%的寬度,而middle的內容設置左右margin各為100px,這樣就實現了middle居中自適應寬度的效果:
<div style="width:100%;"> <div style="margin: 0 100px;">middle</div> </div>
接下來讓left的margin-left值為-100%,由於這個比例是相對於瀏覽器窗口大小的,所以要是left和middle是在同一行的話,left就可以跑到middle的最左邊。但是因為middle的容器盒是一個普通的div,會占據一整行,left就會排到下一行,這個時候設置margin-left為一個負數時就跑出屏幕了。所以讓middle float一下,left就會排到第一行最左邊,同時middle覆蓋在上面:
<div style="width:100%; float:left;"> <div style="margin: 0 100px;">middle</div> </div> <div style="margin-left: 0">left</div>
從上面可以看到:這樣實現,導致left的內容被擠出目標區域,因為正如上面所說,middle占據了left的背景空間,上面的情況是把它占滿了,left內容只能overflow了。所以這樣實現是有問題的,得讓left也向左float一下,這樣left就緊挨在middle后面了,由於middle占了100%的寬度,所以再讓left向左邊margin了-100%后,left就剛好在最左邊了。
<div style="width:100%; float:left;"> <div style="margin: 0 100px;">middle</div> </div> <div style="float: left; margin-left: -100%;">left</div>
注意這里,雖然left float之后看起來也是被排到下一行了,但和默認的div獨占一行是不一樣的。float之后的left仍然和middle是同一行的,因為空間不足的時候,float只是把當前行盒的空間撐大,就和一個div塊盒里面有很多個display為inline-block的行內級盒是同樣的道理。例如:
<style> button{ width: 150px; } </style> <div style="width: 300px;"> <button>按鈕1</button> <button>按鈕2</button> <button style="margin-left: -200px;">按鈕3</button> </div>
按鈕3設置了一個很大的margin-left的負值后並不是跑到屏幕外了,而是在和其它兩個按鈕同一行的位置,顯示如下:
注意,設置了float的元素並不是把display改成了 inline-block,大部份display的css計算值都變成了block,同時對原本是display: flex的沒有改變:
指定值 | 計算值 |
---|---|
inline |
block |
inline-block |
block |
inline-table |
table |
table-row |
block |
table-row-group |
block |
table-column |
block |
table-column-group |
block |
table-cell |
block |
table-caption |
block |
table-header-group |
block |
table-footer-group |
block |
flex |
flex , but float has no effect on such elements |
inline-flex |
inline-flex , but float has no effect on such elements |
other | unchanged |
來自MDN
由上表可看出,一個span設置了float: left/right之后,就不需要再設置成display: block/inline-block了,直接設置寬高即可。
回歸正題,left的div設置了margin-left: -100%之后就跑到左邊去了,而right也是同樣道理,將right的margin-left相應地設置為-100px,就跑到最右邊去了:
<div style="width:100%; float:left;"> <div style="margin: 0 100px;">middle</div> </div> <div style="margin-left: -100%;">left</div> <div style="margin-right: -100px;">right</div>
Action 9 margin負值法
下面討論第三種方法,使用display: table-cell
由於table的展示擁有自適應的特點,因此把需要自適應寬度的middle的display屬性設置為table-cell。
<div style="float:left;">left</div> <div style="float:right;">right</div> <div style="display:table-cell;">middle</div>
效果如下:
發現table-cell的寬度是根據內容自適應的,這里是要根據瀏覽器窗口自適應,因此給middle添加一個很大的width就可以了,例如width:2000px:
由於ie6/7不支持display: table-cell,所以如果要支持ie6/7的話,就得用display: inline-block,ie6/7的inline-block和標准不一樣,它是用來觸發hasLayout特性,使元素擁有布局屬性。作用在行內元素,可以使得寬高等設置生效,如果作用在塊元素,僅是觸發布局特性,還要再設置成inline才是行內塊元素,如果不設置inline效果就跟table-cell很像。不一樣的地方是:設置了width:2000px,導致太長會換行,因此得用ie6/7的hack,設置*width: auto重新改會width值就可以了:
<style> .middle{ display: table-cell; *display: inline-block; /* _和*開頭的只有ie6/7會識別 */ width: 2000px; *width: auto; } </style> <div style="float:left;">left</div> <div style="float:right;">right</div> <div class="middle">middle</div>
但是筆者認為:為了互聯網的美好,不要再兼容ie6/7了,甚至ie8。
接下來,繼續第四種方法,使用flex布局,十分簡單:只需要將容器設置為display: flex,然后再設置middle的flex-grow為1即可:
<section style="display:flex;"></section> <div>left</div> <!--寬度為100,省略--> <div style="flex-grow: 1;">middle</div> <div>right</div>
Action 11 使用flex-grow自適應寬度
flex-grow: 1的作用是把middle的寬度置為flex容器的剩余寬度,就達到了自適應的目的。flex的使用不作全面介紹,詳情可查看CSS-Tricks: A Complete Guide to Flexbox
但是flex布局ie的支持性較差,具體查看caniuse.
最后再分析另外一個自適應的例子,某個元素的寬度要根據其它元素的寬度自適應。如下圖所示,排名的位數變化可能會很大,導致最右邊的文字要自適應:
根據上面的一番分析,這個例子就不難實現了,如下面的分析,p標簽里的文字寬度就能自適應了:
<div style="width:320px;"> <span style="width:14px;float:left;">排名</span> <span style="font-size:40px;float:left;">89</span>
<img style="width:44px;height:44px;float:left;" src="..."></img> <p>你的好友會編程的銀豬在土壕榜中排名89</p> </div>
實際效果:

你的好友會編程的銀豬在土壕榜中排名1

你的好友會編程的銀豬在土壕榜中排名6783
使用float是最簡單的,還可以嘗試使用flex布局,主要用到flex-shrink屬性,flex-shrink的作用是定義收縮比例,容器內的子元素的寬度和若超出容器的寬度時,將按比例收縮子元素的寬度,使得寬度和等於容器的寬度。如下所示,將前面三個span/img的flex-shrink設置為0,而p的flex-shrink設置為1,這樣子使得溢出的寬度都在p標簽減去,就能夠達到p標簽寬度自適應的效果。
<style> span,img{ flex-shrink: 0; } p{ flex-shrink: 1; } </style> <div style="display:flex;width:320px;"> <span style="width:14px;">排名</span> <span style="font-size:40px;line-height:45px;">89</span> <img style="width:44px;height:44px;" src="..."></img> <p>你的好友會編程的銀豬在土壕榜中排名89</p> </div>
實時效果:

你的好友會編程的銀豬在土壕榜中排名89

你的好友會編程的銀豬在土壕榜中排名1890
上文綜合分析了最原始的table布局,然后就是float布局、table-cell、margin負值法、以及flex布局來實現自適應寬度的實現和原理,重點討論了float的一些特性。如果上面的分析有錯誤,還望指正。
個人博客: http://yincheng.site/css-layout
參考:
1. CSS Float Theory: Things You Should Know
2. CSS Tricks: All About Floats
5. 視覺格式化模型