前言
因為要在團隊內作一次技術分享,想了幾個題目,最后還是決定系統總結一下我在CSS布局這方面的知識。一是這個題目不算艱深,比較符合目前我的水平,同時也方便團隊的設計和其他偶爾幫忙切切頁面的小伙伴理解;二是從入行以后學得就比較雜亂,長此以往也不是好事,今年希望好好總結沉淀一下學到東西;三是剛剛過去的2016年正好是CSS正式誕生20周年(1996年12月17日,CSS第一版標準正式發布),我的首次技術分享取這個題目也有紀念意義。另外,準備分享的那天同時穿上第三屆CSS CONF送的T恤,嘿嘿。
先來說說過去和現在的常見布局吧
古老的table布局
記得我在大學時候,有一門課程叫網頁設計和網站制作,學的就是HTML和CSS來制作一些頁面,用的還是Dreamweaver來寫代碼,那時老師課堂上教的就是table布局。當時的我怎么也不會想到幾年之后會靠一行吃飯,人生真是充滿了戲劇性。
作為十多年前的主要布局方案,table布局的存在也是有一定道理的。table
系列標簽天生自帶的柵格、伸縮和內容垂直居中等特性能很方便地解決布局排版的許多痛點,特別是在那個CSS還不夠強大,或者說更多深入的CSS運用還沒有被人挖掘出來的年代。不過這個布局方案的缺點也是顯而易見的:完全背離HTML標簽的語義;標簽冗余;擴展性和自由度不高。-
DIV和CSS時代
從這時起,網頁的布局算是正式進入現代了。div
標簽全稱的單詞是division
,顧名思義,每一個div
就是一個分隔出來獨立的區域,它和CSS中float
,position
和display
等屬性的結合,在今天仍然是我們布局排版的主要手段。甚至在五六年前吧,市面上有很多這方面的技術書取名就用了DIV和CSS,搞得好像DIV是某一個專門的技術似的(想起半月前知乎那個SUV+CSS的梗了,哈哈哈哈),其實就是HTML里的一個塊級標簽,業界都拿它來用想必也是因為它的默認塊級屬性和其本身是唯一不帶任何自身樣式,最為“干凈”的塊級標簽吧。目前主流的基于DIV和CSS的布局,主要利用的也是CSS(CSS2)中的三塊核心內容:浮動、定位和盒子模型。關于這些內容網上的教程已經講得很清楚了,下面主要是思路介紹和一些個人覺得需要注意的地方。
比如我們利用左右浮動,可以讓元素脫離原本的文檔流,貼到父級元素的最左或者最右邊,當然這中間可能會有需要
清除浮動
的情況,另外值得注意的是:其一,浮動后的元素會脫離文檔流,平面看上去雖然還是在一層,然而垂直方向上已經高于相鄰的其他元素了;其二,浮動屬性最初設計是為了實現文字圖片環繞的排版需求的,因此,個人覺得開始用它作為塊級元素的排版本身也是對屬性利用的深度挖掘吧。利用定位屬性,我們可以隨心所欲地將目標元素放在參照物內的任何地方,然而如果定位真有這么完美又怎么可能會出現那么多其他的布局方案呢?就拿絕對定位來說吧,我們定位排好一個元素之后,試著縮小瀏覽器的寬度到一定程度,定位元素就會和相鄰元素發生重疊的情況。此外,關于絕對定位的參照物問題也需要說明,過去有很多資料(甚至包括w3cschool)指出絕對定位的參照元素是從
absolute
開始,向上一直追溯到定位屬性不是默認的static
的元素,如果都沒有,那就以<html>
,有的也說是<body>
為參照。其實關于這一點在官方標準里說得很明確:
<blockquote>
The position and size of an element's box(es) are sometimes calculated relative to a certain rectangle, called the containing block of the element.If the element has 'position: absolute', the containing block is established by the nearest ancestor with a 'position' of 'absolute', 'relative'or 'fixed', in the following way: In the case that the ancestor is an inline element, the containing block is the bounding box around the padding boxes of the first and the last inline boxes generated for that element. In CSS 2.1, if the inline element is split across multiple lines, the containing block is undefined. Otherwise, the containing block is formed by the padding edge of the ancestor.
If there is no such ancestor, the containing block is the initial containing block.
關于initial containing block :The containing block in which the root element lives is a rectangle called the initial containing block. For continuous media, it has the dimensions of the viewport and is anchored at the canvas origin...
</blockquote>
簡而言之,絕對定位的參照元素是其最近的具有定位屬性為absolute/relative/fixed三者之一的祖先元素的包含塊來定位;如果沒有,則追溯到根元素的包含塊,即初始包含塊(這里不是根元素html,我們可以給html設一個寬高進行實驗,發現定位的元素還是相對于整個窗口而言的),這個初始包含塊可以簡單理解為視窗形成的矩形,其原點是左上角(canvas origin)。再啰嗦一點關于相對定位和絕對定位的區別,相對定位的元素依然會進入文檔流,會保留它在文檔流中的占位,而絕對定位的元素則會脫離文檔流(這也是為什么我們通常選擇給父元素設置relative
來作為子元素絕對定位的參照,因為不給它任何偏移量的話它依然還是原來在文檔流中的位置)。
至于盒子模型,則主要是利用內外邊距margin
和padding
來進行局部調整布局了。需要注意的就是盒模型寬高的計算,還有IE和非IE瀏覽器下盒模型渲染的差異,以及相鄰元素margin重合的情況,當然也記得使用負邊距這個腦洞(雖然似乎官方并不推薦使用負值,border-radius
負值渲染為內向圓角的提案就曾被駁回了=_=)。
-
圣杯和雙飛翼布局
基于上面提到的CSS屬性的利用,前輩們創造出了兩欄,三欄,定寬+自適應等各種組合的頁面布局方案,這里主要選擇兩個最具代表性的布局來作為示例。圣杯布局(Holy Grail)其實就是左右兩列定寬,中間內容自適應三列布局的一種算是完美的實現。過去的三欄布局基于文檔流的順序,需要
left
,main
,right
三列依次渲染,而圣杯布局則解決了這一問題,它可以:
<blockquote>
兩邊帶有固定寬度中間可以流動伸縮(fluid)
允許中間一欄最先出現在文檔中
僅需一個額外的div標簽
僅需非常簡單的 CSS,兼容性良好
</blockquote>下面是主要代碼:
/* HTML */ // 可以看到只多了一個額外的container,并且主要內容main如愿放在了文檔流的前面 <div class="header">頭部信息</div> <div class="container"> <div class="main col">主要內容</div> <div class="left col">左側內容</div> <div class="right col">右側內容</div> </div> <div class="footer">底部信息</div> /* CSS */ .header, .footer { background: #ccc; color: white; text-align: center; } .container { padding: 0 100px 0 200px; /* 設置container的左右內邊距為左右兩欄的寬度值 */ color: white; text-align: center; } .container .col { float: left; /* 為三列都設置左浮動,此時左右兩列還是被中列擠在下方 */ position: relative; /* 都設置相對定位 */ } .main { width: 100%; /* 中間寬度設置百分比 */ background: green; } .left { left: -200px; /* 其實也可以是right: 200px; 移到左邊占據padding的位置 */ width: 200px; margin-left: -100%; /* 左側負margin100%,這個100%是container的寬度,此時到了上方 */ background: red; } .right { width: 100px; margin-right: -100px; /* 移到右邊占據右側padding的位置 */ background: blue; } .footer { clear: both; /* 清除footer周圍的浮動,避免上移 */ } /* 需要注意的是沒有清除三列的浮動,container實際上是沒有包住三列的 當然清除三列的浮動也可以實現同樣的效果 */
雙飛翼布局則是根據圣杯布局改變而來,在原本的基礎上多增加了一層div嵌套,而省去了相對定位的設置,并且可以自由地利用margin屬性給中列設置左右的間隙,下面是主要代碼:
/* HTML */ // 原本main層的內容多用一個content層嵌套 <div class="header">頭部信息</div> <div class="container"> <div class="main"> <div class="content">主要內容</div> </div> <div class="left">左側內容</div> <div class="right">右側內容</div> </div> <div class="footer">底部信息</div> /* CSS */ .header, .footer { background: #ccc; color: white; text-align: center; } .container { /* 不再需要原本container的內邊距 */ color: white; text-align: center; } .main { float: left; width: 100%; } .main .content { /* 利用content的外邊距來預留左右空間,并且多出來的數值會成為間隙 */ margin: 0 220px 0 320px; background: red; } .left { float: left; width: 300px; margin-left: -100%; background: green; } .right { float: left; width: 200px; margin-left: -200px; background: blue; } .footer { clear: both; }
其實對于圣杯和雙飛翼布局,我們還可以在這個基礎上進行發揮修改,比如兩列自適應單列定寬,比如左側自適應或者右側自適應等等,有興趣的同學可以自己試試。
-
瀑布流
所謂瀑布流,就是一列列長條狀的高度不一的盒子縱向排列,再配上JS做出下滑動態更新添加的效果,看上去像是瀑布一樣。關于瀑布流布局我也沒有在實際項目中運用到,大致了解通常是需要利用JS來計算列的數量和位置。不過CSS3提供的多列布局屬性能讓我們更加方便地實現這個效果,當然,方便背后的代價往往就是兼容性問題,不過除了IE,主流瀏覽器的支持情況已經很不錯了。
<a >點擊這里</a>可以簡單看看CSS3多列屬性的介紹,下面是示例代碼:/* HTML */ <div class="main"> <div class="pic pic1"> <h3>111</h3> </div> <div class="pic pic2"> <h3>222</h3> </div> <div class="pic pic3"> <h3>333</h3> </div> <div class="pic pic4"> <h3>444</h3> </div> <div class="pic pic5"> <h3>555</h3> </div> <div class="pic pic6"> <h3>666</h3> </div> <div class="pic pic7"> <h3>777</h3> </div> <div class="pic pic8"> <h3>888</h3> </div> <div class="pic pic9"> <h3>999</h3> </div> <div class="pic pic10"> <h3>1010</h3> </div> </div> /* CSS */ * { padding: 0; margin: 0; } .main { width: 1200px; margin: 60px auto; padding: 20px; box-shadow: 2px 2px 6px #ccc; column-width: 218px; /* 大盒子的寬度除以列寬會計算出有多少列 */ column-count: 5; /* 也可以自己指定列數 */ column-gap: 5px; /* 列間距其實自己也會計算 */ } .main .pic { display: inline-block; /* 加上這個屬性讓每一塊不錯位 */ width: 188px; min-height: 100px; padding: 0px 15px; margin: 10px 0; box-shadow: 2px 2px 6px #ccc; } .main .pic h3 { line-height: 100px; text-align: center; } /*給一些高度加以區分*/ .pic1, .pic3 { height: 120px; } .pic2 { height: 180px; } .pic5 { height: 160px; } .pic7, .pic10 { height: 130px; } .pic8 { height: 150px; }
- 利用display的其他布局辦法
CSS的display屬性還為我們提供了更多的布局辦法,比如利用inline-block
的行內特性實現多行元素在不同容器寬度下顯示不同個數的效果,然而痛點是解決左右兩側不居中的問題;再有比如display: table
和display: table-cell
利用表格實現垂直居中,并且支持文本內容縱向擴展為多行的效果。下面則是個人認為未來將成為主流的布局方案。
承前啟后的彈性盒子display: flex
對于這個屬性,前后出現過幾次候選標準,所以網上搜索的時候往往會出來很多不同的屬性名稱,可能會讓人感到困惑,同樣它的兼容性問題在現在仍然是一個很煩人的事,明明這么方便的屬性卻不能痛快地使用。彈性盒子的教程我推薦兩個,看過之后其實已經完全可以上手用起來了,今后如果我有更多的使用心得也會再做筆記:
<a >這個網站除了flexbox也有講其他布局的基礎知識,非常推薦</a>
<a >阮一峰flex布局教程:語法篇</a>
<a >阮一峰flex布局教程:實戰篇</a>
阮一峰的教程已經將這個屬性講得足夠透徹了,實戰部分也為我們提供不少布局案例的flex實現方法,這里我也就不再贅述。關鍵就是理解主軸和縱軸的概念。
即將支持的新標準 CSS3 Grid Layout
display: grid
這個屬性,我是在第三屆中國CSS CONF上通過大漠老師的介紹了解到的,目前已經成為候選標準,而據大會主持人透露,似乎今年(2017)各大主流瀏覽器就將全面支持該屬性,更可喜的是這個標準是由微軟提出的,而且早已率先在IE10實現了。( ⊙o⊙ )
<a >W3C網站上</a>有對于該屬性詳細的介紹和教程,雖然目前幾乎不太可能用于實際的項目,但還是列在這里也是希望能夠對這個新的技術方案保持關注。
就我在大會上看到的演示的直觀感受來說,這個屬性的強大完全不亞于display: flex
,并且作為網格布局,比flexbox更適合作為頁面大結構的布局方案,而flexbox更適合盒子內容的排版,相信正如大漠老師所看好的,今后二者的結合或許會成為我們主流的布局手段。