上一篇文章《聖杯佈局小結》總結了幾種常見的分欄佈局方法,這幾個方法都可以實現多欄頁面下,所有欄的高度可動態變化,某一欄寬度自適應的佈局效果,能滿足工作中大部分的佈局需求。後來我在搜集更多關於分欄佈局的文章時,發現了一個新的問題,這個問題在前面那篇文章中也有朋友在評論里跟我提起,就是如何在實現分欄佈局...
上一篇文章《聖杯佈局小結》總結了幾種常見的分欄佈局方法,這幾個方法都可以實現多欄頁面下,所有欄的高度可動態變化,某一欄寬度自適應的佈局效果,能滿足工作中很多佈局需求。後來我在搜集更多關於分欄佈局的文章時,發現了一個新的問題,這個問題在前面那篇文章中也有朋友在評論里跟我提起,就是如何在實現分欄佈局的同時保證每欄的高度相同。我發現這種等高分欄佈局的情況,在網站裡面其實也很常見,所以本文總結了幾種可用的方法來解決這個新的需求。
1. 方法一:萬能的flex
跟上篇文章不同,這次把flex這種方法放在了第一位,因為相比較起來,它是所有分欄佈局方法裡面,優點最多的,如果相容性允許的話,很有必要在任何時候都優先使用它完成頁面佈局。如果你打開上篇文章,找到倒數第二部分關於flex實現分欄佈局的代碼,或者把上篇文章提供的代碼下載下來,直接預覽flex_layout.html,你會發現上篇文章的那段代碼其實已經做到了等高分欄佈局,同一段代碼,可以實現上篇文章中提到的五種分欄佈局,還可以實現本文提到的等高佈局的情況,這種能力其它方法真的無法比擬。而它之所以能實現等高佈局,跟一個flex的css屬性有關係,這個屬性是:align-item。它的預設值是:stretch,在flex item元素比如layout__main或layout__aside的高度未定義或者為auto的情況下,會拉伸flex item元素的高度或寬度,鋪滿flex的交叉軸,詳細的原理可以通過上文提供的flex學習資源去瞭解,這裡只做一個簡單的引用說明。
2. 方法二:使用table或者偽table
上篇文章中還有另外兩種佈局方法沒有介紹,第一種就是這裡要說的table佈局或者偽table佈局。table佈局用的就是table tr td這些元素去實現,相信絕大部分web開發人員在入門html時,首先接觸到的佈局方法肯定就是table佈局了,這種方法簡單高效,用它做任何分欄佈局都不是問題,只是因為table的嵌套結構太多,html冗雜,又不利於DOM的操作和渲染,用來佈局不符合語義,總之缺點較多,所以目前的環境下,用的情況越來越少了。偽table佈局其實跟table佈局類似,只不過藉助於css,可以讓我們不直接使用table tr td這些直接的表格元素,而是通過display: table, display: table-row, display: table-cell,改變元素的顯示特性,讓瀏覽器把這些元素當成table來渲染,這種渲染的表現跟用真實的table沒有啥區別,就連那些table專用的css屬性,比如table-layout,border-collapse和border-spacing,都能產生效果。table佈局的方法已經很少被採用了,本文也就沒必要再去介紹,但是偽table佈局的方法值得學習一下,經過這兩天的學習,發現偽table的方式相比直接用表格佈局,有不少的優點,值得運用到工作中去。不過在說明使用偽table佈局的方法之前,得先瞭解一些偽table相關的知識:
1)可用於偽table表現的display屬性值有:
2)當把一個元素的display屬性設置成以上列出的值後,就可以把這個元素看成與該屬性對應的表格元素,比如table-cell對應的就是td;同時,這個元素會擁有跟表格元素一樣的特性,比如display: table或者inline-table的元素可以使用table-layout,border-collapse和border-spacing這三個原本只有table才能生效的屬性;display:table-cell的元素跟td一樣,對寬度高度敏感,對margin值無反應,對padding有效。
3)關於table-cell還有一點要說明的就是,它會被其他一些CSS屬性破壞,例如float, position:absolute,所以這些個屬性不能同時使用。
4)跟直接使用表格元素不同的是,在使用表格元素的時候需要完全遵守表格元素嵌套結構,也就是下麵這種:
<table> <thead> <th></th> </thead> <tbody> <tr> <td></td> </tr> </tbody> <tfoot> <th></th> </tfoot> </table>
而使用偽table的那些屬性時,可以僅單獨使用某一個屬性,瀏覽器會在這些元素的外層包裹缺失的框來保證偽table元素框嵌套結構的完整性,這些框跟常提到的行框一樣都是不可見的,網上有的文章里也把這種做法叫做匿名錶格。下麵的這個代碼中,tb-cell元素的外層沒有加display: table-row和display: table的元素:
.tb-cell {
display: table-cell;
padding: 10px;
border: 1px solid #ccc;
}
<div class="tb-cell">這是第1個display: table-cell;的元素。</div>
<div class="tb-cell">這是第2個display: table-cell;的元素。</div>
但是看到的效果是(藍色背景是它們父層的一個包裹元素: width: 800px;margin-left: auto;margin-right: auto):
因為瀏覽器自動在這兩個元素的外層,加了跟能夠跟tr和table起相同作用的框,來包含這兩個元素形成的框,所以這兩個元素看起來就跟實際的表格效果一樣。假如瀏覽器沒有做這個處理,這兩個元素之間是不可能沒有間隙的,中間會有一個因為換行符顯示出來的空格。這種自動添加的框都是行內框,不是塊級框。
接下來看看如何通過這些偽table的屬性來完成上文的分欄佈局以及本文要求的等高分欄佈局,玩法有很多:(本文相關源碼下載)
玩法一:模擬直接用表格佈局(對應源碼中table_layout1.html)
這種方法的思路是佈局時完全按照表格的嵌套層次來處理,把display: table, display: table-row, display: table-cell都用上,相當於就是利用完整的table來做,比如說要實現上文的佈局三(3欄佈局,2個側邊欄分別固定在左邊和右邊,中間是主體內容欄),就可以這麼乾:
<div class="layout"> <div class="layout__row"> <aside class="layout__col layout__aside layout__aside--left">左側邊欄寬度固定</aside> <div class="layout__col layout__main">內容欄寬度自適應<br>高度增加一點,旁邊的高度都會自動增加</div> <aside class="layout__col layout__aside layout__aside--right">右側邊欄寬度固定</aside> </div> </div>
<style type="text/css"> .layout { display: table; width: 100%; } .layout__row { display: table-row; } .layout__col { text-align: center; display: table-cell; } .layout__col + .layout__col { border-left: 10px solid #fff; } .layout__main { background-color: #4DBCB0; } .layout__aside { width: 200px; background-color: #daf1ef; } </style>
效果還是那個效果,而且天生支持等高佈局:
這個佈局原理跟使用table是完全一樣的,所以使用起來非常容易(以上提供的是針對上文佈局三的實現,其它四個佈局的實現不會再一一介紹了,源碼裡面也不會提供,因為相對比較簡單)。
這種偽table佈局有什麼特點呢:
1)相比直接用表格元素,這種做法不需要考慮語義,表格元素是有語義的,主要是用來顯示網頁上列表型的數據內容,雖然可以完成佈局,但是佈局結構都是沒有語義的,所以直接用表格不合適,而這種偽table佈局的特點就是:它沒有語義,但是可以像表格那樣佈局;
2)html的層次結構相比直接用table元素也要簡單一些,我們這裡只用到了3層,直接用table元素的話可能還有tbody這一層;
3)相比上文提到的那些佈局方法,如聖杯佈局和雙飛翼佈局,這個做法在css方面相對簡單,在html方面也只多了一層嵌套;
4)缺點是分欄之間的間隔不能用margin和padding來做,如果用margin,這個屬性在display: table-cell的元素上根本不會生效;如果用padding,那像demo裡面各欄的背景色就都會連到一塊,做不出間隔的效果,如果在layout__col裡面再嵌套一層,在這一層設置背景色的話,又會增加html的層次,也不是很好。我這裡是投了個巧,用border處理了一下。
玩法二:去掉display: table-row(對應源碼中的table_layout2.html)
前面說過,瀏覽器會用匿名錶格的方式,添加缺失的框,所以玩法一中的代碼,把layout-row完全去掉,一點都不影響佈局效果:
<div class="layout"> <aside class="layout__col layout__aside layout__aside--left">左側邊欄寬度固定</aside> <div class="layout__col layout__main">內容欄寬度自適應<br>高度增加一點,旁邊的高度都會自動增加</div> <aside class="layout__col layout__aside layout__aside--right">右側邊欄寬度固定</aside> </div>
<style type="text/css"> .layout { display: table; width: 100%; } .layout__col { text-align: center; display: table-cell; } .layout__col + .layout__col { border-left: 10px solid #fff; } .layout__main { background-color: #4DBCB0; } .layout__aside { width: 200px; background-color: #daf1ef; } </style>
玩法三:去掉display: table(對應源碼中的table_layout3.html)
根據玩法二,可以試想一下是否能再把display: table這一個屬性給去掉,反正瀏覽器還會再添加框來包裹:
<div class="layout"> <aside class="layout__col layout__aside layout__aside--left">左側邊欄寬度固定</aside> <div class="layout__col layout__main">內容欄寬度自適應<br>高度增加一點,旁邊的高度都會自動增加</div> <aside class="layout__col layout__aside layout__aside--right">右側邊欄寬度固定</aside> </div>
<style type="text/css"> .layout__col { text-align: center; display: table-cell; } .layout__col + .layout__col { border-left: 10px solid #fff; } .layout__main { background-color: #4DBCB0; } .layout__aside { width: 200px; min-width: 200px; background-color: #daf1ef; } </style>
效果是:
這個並沒有達到我們的效果,因為我需要主體內容欄能夠自適應寬度。產生這個效果的原因是什麼,就是因為沒有加顯示display: table這一層,瀏覽器自動加了一個框,不過這個框是行內框,導致主體內容欄顯示的寬度就跟內容的寬度一致了。為瞭解決這個問題,可以這麼乾,html結構不變,css稍加改動:
.layout__main {
width: 3000px;
background-color: #4DBCB0;
}
.layout__aside {
width: 200px;
min-width: 200px;
background-color: #daf1ef;
}
關鍵的代碼就是紅色新增的那兩行,首先給主體內容欄設置一個很長的寬度,而且只能用具體的長度設置,不能用百分比,然後給側邊欄設置一個最小寬度,免得主體內容欄把側邊欄的寬度給擠掉了。這個原理就是因為display: table-cell的作用,導致layout__main跟layout__aside表現出跟td元素一樣的特性,td預設的寬度就是可自動調整的,即使寬度設置的很大,也不會撐破table的寬度,這裡雖然那個自動添加的框看不到,但是這個框的最大寬度也就是瀏覽器的寬度,layout__main不會打破這個寬度的,所以可以放心使用。
玩法四:去掉layout這一層包裹元素(對應源碼:table_layout4.html)
如果網站比較簡單,去掉layout這一層包裹元素也是可以的:
<header>頂部</header> <aside class="layout__col layout__aside layout__aside--left">左側邊欄寬度固定</aside> <div class="layout__col layout__main">內容欄寬度自適應<br>高度增加一點,旁邊的高度都會自動增加</div> <aside class="layout__col layout__aside layout__aside--right">右側邊欄寬度固定</aside> <footer>底部</footer>
<style type="text/css"> .layout__col { text-align: center; display: table-cell; line-height: 50px; } .layout__col + .layout__col { border-left: 10px solid #fff; } .layout__main { width: 3000px; background-color: #4DBCB0; } .layout__aside { width: 200px; min-width: 200px; background-color: #daf1ef; } </style>
以上四種做法都能實現我們想要的分欄等高佈局,相容性方面,不考慮IE8及以下,其它瀏覽器幾乎沒有問題。
由於匿名錶格的作用,導致採用偽table佈局的方法變得非常簡潔,上文之所以沒提到這個做法,是因為完全不知道有匿名錶格這回事,我也是寫這篇文章才學習到的,學完之後,發現又找到了一個做分欄佈局的好辦法,希望前面的這些介紹能幫助你掌握好這個用法。實際上偽table的這些屬性,尤其是table-cell,用途非常多,本文沒有辦法一一介紹,但是能提供一個思路,將來工作中也許有很多其它佈局場景,我們都可以想想用table-cell來處理。
3. 方法三:使用絕對定位
上文沒有介紹的另外一種分欄佈局方法就是這裡要介紹的絕對定位。之所以沒介紹這個方法,是因為上文介紹的都是分欄自適應佈局的方法,而絕對定位的做法,不能完全做到我們想要的分欄自適應佈局,分欄自適應有兩個原則:第一是主體內容欄寬度自適應,這點絕對定位是可以做到的;第二點是所有欄的高度都能動態變化,並且不能導致父容器高度塌陷,不能在各欄內部出現滾動或溢出的情況,這點絕對定位不容易做到適用所有場景。而本文又把這種佈局方法拿出來介紹,是因為絕對定位做等高佈局很容易,所以用絕對定位做等高分欄佈局是一種可行的辦法,只是這種方法適用的場景有一些限制,需要根據實際情況考慮是否要採用。
做法一:所有欄都採用絕對定位(對應源碼中absolute_layout1.html)
<header>頂部</header> <div class="layout"> <aside class="layout__aside layout__aside--left">左側邊欄寬度固定</aside> <div class="layout__main">內容欄寬度自適應</div> <aside class="layout__aside layout__aside--right">右側邊欄寬度固定</aside> </div> <footer>底部</footer>
<style type="text/css"> .layout { height: 300px; position: relative; } .layout__aside, .layout__main { position: absolute; top: 0; bottom: 0; } .layout__main { left: 210px; right: 210px; } .layout__aside { width: 200px; } .layout__aside--left { left: 0; } .layout__aside--right { right: 0; } </style>
效果:
這種佈局方法的特點是:
1)主體內容欄是自適應的;
2)所有欄完全等高,效果跟flex佈局和偽table佈局的效果一樣;
從這兩點來看,這種絕對定位的方法還是比較好用的,不過它有一個非常大的使用限制,就是父元素的高度沒有辦法通過它的內部元素給撐起來,要用的話,必須想辦法讓父元素有高度,適合做父元素高度可知或者全屏佈局。比如以下這個代碼就是全屏佈局的一個例子(對應源碼中absolute_layout2.html):
<header>頂部</header> <div class="layout"> <aside class="layout__aside layout__aside--left">左側邊欄寬度固定</aside> <div class="layout__main">內容欄寬度自適應</div> <aside class="layout__aside layout__aside--right">右側邊欄寬度固定</aside> </div> <footer>底部</footer>
<style type="text/css"> html,body { margin: 0; height: 100%; } footer { position: absolute; bottom: 0; width: 100%; } .layout { width: 100%; position: absolute; top: 50px; bottom: 50px; } .layout__aside, .layout__main { position: absolute; top: 0; bottom: 0; } .layout__main { left: 210px; right: 210px; } .layout__aside { width: 200px; } .layout__aside--left { left: 0; } .layout__aside--right { right: 0; } </style>
效果:
做法二:側邊欄絕對定位,主體內容欄保持流式佈局(對應源碼中absolute_layout3.html)
<div class="layout"> <aside class="layout__aside layout__aside--left">左側邊欄寬度固定</aside> <div class="layout__main">內容欄寬度自適應<br>高度增加一點,旁邊的高度都會自動增加</div> <aside class="layout__aside layout__aside--right">右側邊欄寬度固定</aside> </div>
<style type="text/css"> .layout { position: relative; } .layout__aside { position: absolute; top: 0; bottom: 0; } .layout__main { margin: 0 210px; } .layout__aside { width: 200px; } .layout__aside--left { left: 0; } .layout__aside--right { right: 0; } </style>
效果:
這個方法的特點是:
1)主體內容欄是寬度自適應的;
2)所有欄也是完全等高的;
上面的代碼中,layout__main通過magin來給側邊欄留出空間,其實也可以在layout元素上添加padding來處理,作用是一樣的。這個方法相比前一個方法好一點的是,父元素的高度可以通過主體內容欄給撐起來,不過由此也帶來了一個新問題,就是內容欄高度不夠的時候,側邊欄就會出現溢出或者滾動,解決這個新問題的辦法有2個:第一,如果側邊欄的內容都是已知的,並且沒有摺疊展開這種會改變側邊欄內容高度的功能,那麼可以給layout設置一個min-height來處理;第二,如果側邊欄的內容是動態的,除了給layout加min-height之外,還得在每次改變側邊欄內容的時候,主動去調整主體內容欄的高度,如果主體內容欄的高度小於側邊欄的高度,就要更新主體內容欄的高度。不過如果你的內容欄的內容很多,側邊欄內容較少的話,就不用考慮這個新問題了。
絕對定位的做法就是這樣,第一種限制較高;第二種稍微強一些,在一些場景下,可能還得藉助JS來處理,所以綜合起來不算是一個非常好的方式。只有你的佈局需求恰好滿足它的條件時,可能才會考慮使用它,就像上文中我提出的項目一的需求,就一定要用絕對定位的佈局來做。
4. 方法四:藉助邊框,背景實現假等高
前面介紹了幾種分欄等高佈局,有table佈局,偽table佈局,絕對定位佈局,flex佈局,這四種佈局方法在實現等高佈局時,屬於完全等高的情況,就是說他們佈局出來的頁面,各欄的真實高度都是相同的,並且在任意欄的內容動態變化時,其它欄的高度都能相應地自動調整,如果佈局的時候用的是這幾個佈局方法,那麼等高的問題就不存在了。不過回看一下上文內容的話,上文提到的3種佈局方式:聖杯佈局,雙飛翼佈局,float佈局,不用JS的話,就無法做到這種完全等高的效果。這三種佈局,只能考慮藉助邊框和背景實現視覺上的等高,也就是假等高的做法。畢竟從效果上來說,如果沒有設置背景和邊框的話,即使是完全等高,視覺上也看不出來,所以假等高的做法是值得採用的。
做法一:利用背景圖片
以佈局容器寬度固定的左中右三欄佈局說明這個做法的步驟,首先製作一張高度較小,寬度跟佈局容器寬度相同的背景圖片,把這張圖片作為佈局容器的背景圖垂直平鋪。這張背景圖要求跟頁面一樣也是分欄,而且每欄的寬度和欄之間的間隔都跟頁面佈局裡面的