從三欄自適應寬度布局到css布局的討論,自適應css
如何?一個三欄調適型配置,左右各100px,中間隨著瀏覽器寬度自適應?
第一個想到的是使用table布局,設定table的寬度為100%,三個td,第1個和第3個固定寬度為100px,那麼中間那個就會自適應了,下面是一個即時的demo:
但是table布局是不推薦的,table布局是css流行之前使用的布局,有很多缺點:當table載入完之前,整個table的都是空白的,table將資料和排版參和在一起,使得頁面混亂,並且table布局修改排版十分麻煩和困難。
如果不用table布局,那麼第二個想到的辦法是採用float,讓左邊的div float left,右邊的div float right,如下邊所示:
Action 1 先讓左右兩個div浮動
float left float right
中間還有一個div,如果將中間的div排在第二:
<div style="float:left;">left</div><div>middle</div><div style="float:right;">right</div>
那麼效果是這樣的:
Action 2 右邊的div浮動到了第二行
left middle right
因為div預設的display為block,如果不設定width的話,區塊層級元素會儘可能多地佔用水平空間。如果設定了width: 200px,效果是這樣的:
Action 3 右邊的div仍然浮動到了第二行
float left middle right
第三個div仍然會換行,因為float right會排到當前行儘可能右邊的位置,即它的容器盒的邊緣或者挨著的上一個float的元素,如果當前行沒有空間的話,會不斷地往下移,直到有足夠的空間。由於middle是一個塊盒,即使設定了width,當前行的空間也會被佔用,所以right只能到下一行才有空間。
同時注意到middle雖然設定了200px,但是看起來和left一樣是100px寬了。這是因為float了的元素雖然在正常的文檔流之內,但是只是讓相鄰(非float)的元素的內容圍繞著它排列,它仍然佔據相鄰元素的background和border空間。如果給middle添加一個白色的border,那麼看起來是這樣的:
Action 4 浮動的元素佔據了文檔流相應元素的背景和邊緣空間
float left middle right
明顯看到,float left的元素佔據了middle的background和border的空間,同時middle的內容圍繞著left排列。理解這點很重要。
假設middle裡面有個p標籤,而p標籤的內容比較長,那麼圍繞的效果是這樣的:
Action 5 浮動的環繞效果
float left middle
環繞的元素一旦超出float元素高度之後,會以正常的寬度顯示
float right
正如上面的注釋一樣,在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
left right middle
如果不設定middle的width,那麼middle將圍繞著left和right環繞,和left一樣,right也會佔用middle的空間。
Action 7 中間的div圍繞著左右的div環繞
left right ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ middle圍繞著left和right環繞,同時left和right佔據著middle的空間 ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~
為了讓middle和left/right中間有一個margin值,設定下middle的左右margin各為110px,這樣就和左右和中間就各有10px的間距:
Action 8 設定中間div的margin值為100px + 10px
left right middle 設定margin: 0 110px;
這種辦法的優點是實現簡單,支援性好。
這種自適應寬度的原理是利用了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>
middle
接下來讓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>
middleleft
從上面可以看到:這樣實現,導致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>
middleleft
注意這裡,雖然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的負值後並不是跑到螢幕外了,而是在和其它兩個按鈕同一行的位置,顯示如下:
按鈕1 按鈕2 按鈕3
注意,設定了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負值法
middleleftright
下面討論第三種方法,使用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>
效果如下:
leftrightmiddle
發現table-cell的寬度是根據內容自適應的,這裡是要根據瀏覽器視窗自適應,因此給middle添加一個很大的width就可以了,例如width:2000px:
Action 10 讓中間的div使用table-cell自適應寬度leftrightmiddle
由於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自適應寬度
leftmiddle flex-grow: 1right
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 你的好友會編程的銀豬在土壕榜中排名1
排名 6783 你的好友會編程的銀豬在土壕榜中排名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 你的好友會編程的銀豬在土壕榜中排名89
排名 1890 你的好友會編程的銀豬在土壕榜中排名1890
上文綜合分析了最原始的table布局,然後就是float布局、table-cell、margin負值法、以及flex布局來實現自適應寬度的實現和原理,重點討論了float的一些特性。如果上面的分析有錯誤,還望指正。
參考:
1. CSS Float Theory: Things You Should Know
2. CSS Tricks: All About Floats
3. CSS-Tricks: display
4. Understanding Floats
5. 視覺格式化模型