CSS佈局在前端開發中像呼吸一樣——再平常不過的事。比如同事A在嚐到了Flexbox佈局的甜頭之後,任何佈局都會以display:flex
打頭陣,同事B因為專案得支援IE10,像避開毒蛇一樣的避開Flexbox佈局方法。你會發現我可能有點嗤笑這樣的行為,我曾經也是這樣的一員,我想為這個問題——當遇到css佈局,你在考慮什麼? 整理一個完整的解決方案。
你在考慮什麼:從什麼樣的HTML結構出發能夠幫助到css佈局?你的佈局方法武器庫都有什麼,在具體場景下,選擇什麼合適的佈局方法?需要做支援舊瀏覽器嗎?Flexbox、Grid這些佈局的方法弄潮兒在舊瀏覽器中的最佳實踐是?等等等等。
本文將介紹我的"答案",歡迎胖友們補充、更正。
Normal flow:css佈局的起點
Normal flow(不知什麼中文翻譯妥帖,還是扔了英文...)指的是如果沒有改變css佈局程式碼,網頁中標籤的預設表現方式。比如demo-normal-flow:塊級標籤p
挨個從上往下,而內聯標籤span
表現得像段落中的文字。
當我們建立、自定義一個佈局,其實是調整標籤在Normal flow中的位置,或是直接從Normal flow移除,我們最最原始的材料就是Normal flow。如果使用語義化標籤(semantic markup),從一個結構良好的HTML文件開始是很有幫助的:
- 語義化標籤確保**內容可讀,**即使是非常受限制的瀏覽器、像螢幕閱讀器這樣的裝置也如此;
- 以此為起點去佈局文件,是合作友好的,而不是破壞性的、改動很大的,因為大多數標籤還是在Normal flow中;
HTML5新加了些幫助結構化的標籤,html-document-structured 這篇文章可以參考,這裡做一個overview:
header
:body
、main
標籤的直接子標籤,位置在頁面頭部,內容可能為logo、標語、搜尋提示、導航欄;nav
:導航欄包在nav
標籤內,可能出現在頭部、側邊欄、底部等等,這裡有個demo-mdn-nav,神奇的地方在於設定nav
標籤的display:inline-block
,是作用在li
標籤上的;main
:body
標籤的直接子標籤,主內容區域;aside
:側邊欄;article
:一般出現在main
標籤內,article
標籤內可以有section
、footer
等標籤,是比較獨立的內容,比如像部落格網站主頁的一個文章簡介;section
:section
和div
很類似,如果使用div
標籤是為了對內容做樣式控制,或者為了便於javascript獲取做其他操作,那麼使用div
就是你的答案,其他情況就用section
;address
:提供聯絡資訊,放在article
標籤內提供文章作者資訊,放在main
、body
、footer
內提供網站資訊;footer
:一般在HTML結構底部,補充網站資訊,如果放在article
內補充文章資訊;
Normal flow是CSS佈局的起點,更好的選擇是語義化標籤(semantic markup)作為CSS佈局的起點。
在具體場景下選擇合適的佈局方法
css佈局方法有很多,如Flexbox、Grid、Float等等等等,在使用之前得把握兩個中心思想:
- 每種佈局方法有它的使用場景、使用上下文,在具體場景中選擇對應合適的佈局方法才是王道;
- 一個頁面往往會應用多種佈局方法,而不是一種佈局方法解決所有問題,佈局方法間是合作的關係;
接下來主要以講demo的形式介紹各個佈局方法的使用場景,對於佈局方法自身如何使用不會過多說明。
Flexbox
Flexbox是Flexible Box Layout的簡稱,Flexbox既可以用於整個頁面的佈局,也可以用於區域性部件的佈局。Flexbox存在些瀏覽器相容性的問題,在舊瀏覽器中的實踐會在之後說明。接下來幾個場景是建立在瀏覽器支援Flexbox的前提下。
Flexing sizing of flex items
Flexbox全稱Flexible Box Layout中的Flexible(靈活性),是它的立命之本。Flexbox的第一個使用場景也呼之欲出——Flexing sizing of flex items,也就是盒子尺寸的高度靈活性:
- demo-flexbox-flex:
section
標籤是Flex容器,article
標籤是Flex item,其中前面兩個article
標籤flex:1 200px
,最後一個article
標籤flex:2 200px
。具體表現為,如果不能提供3個Flex item都是200px
寬度的空間,則它們仨寬度一致,如果能提供,剩餘空間按照1:1:2分配;
- demo-flexbox-flex-fixedWidthWithFlex:這是實際使用中一個很常見的做法,這裡將
footer
標籤高度固定,section
標籤因為flex:1
而佔據餘下所有空間。在水平方向,也可以是側邊欄寬度固定,主要內容佔據餘下所有空間;
水平、垂直位置調整
Flexbox提供像align-items
、justify-content
這樣的屬性去調整flex items在主軸(main axis)、副軸(cross axis)的位置。比如最常見的考試題,水平垂直居中某個元素,demo-flexbox-alignment ;再比如justify-content:space-around
作用於導航條的樣式,demo-flexbox-alignment-justify-content。
調整標籤順序
一般來說,標籤出現順序由原始碼中出現順序決定,Flexbox為Flex items提供了order
屬性,提供從css角度調整Flex items在頁面中出現的順序的能力。
補充一個黑科技
如果為Flex item設定主軸方向(main axis)的margin
值為auto
,比如主軸是橫向的,設定margin-left:auto
,這個Flex item會佔據往左這個方向的剩餘空間:demo-flex-flex-item-margin:auto。
Grid
Grid佈局,和Flexbox設計為在一個方向佈局不同,它幫助我們更加容易地從兩個方向上佈局元素。我更加推薦Grid佈局應用於整個頁面,因為它非常清爽、優雅。它同樣存在瀏覽器相容問題,且比Flexbox更要重,在舊瀏覽器中的實踐會在之後說明。接下來幾個場景是建立在瀏覽器支援Grid的前提下。
優雅的整個頁面佈局
為什麼說它優雅呢?看幾個demo就知道了。
demo-grid-layout、demo-grid-layout-grid-template-areas:兩個demo都實現了最基本的一個頁面情況,一個頭部、一個側邊欄、一個主要內容區域、一個底部,前者是Grid佈局最常規的使用,後者使用了grid-template-areas
屬性;
另外在Grid佈局之前,有一些庫在做模擬Grid System的工作,將一個頁面分成6列或者12列,標籤按列去佔據頁面。Grid佈局方法完全有這樣一個能力,使用12列布局的Grid重寫前面兩個demo實現的效果:demo-grid "framework";
如果能使用Grid佈局整個頁面,我是強烈推薦的,它的思維切入點不再是一維,而是二維,這是一場變革。
Floats
Floats佈局方法既可以針對整個頁面,也可以針對區域性部件,雖然設計之初並不是為了佈局整個頁面。我是把Floats作為無法使用Grid、Flexbox時候的第一選擇。像前面提到的做Grid System的css庫,它其實也是將其中的每一個item設定為了float:left
,然後計算佔據寬度的百分比以模擬Grid System。
另外,"floated item"(設定float:left
或float:right
)會從Normal flow中移除。來看看具體應用的demo吧。
文字環繞圖片
“文字環繞圖片”是Floats設計的初衷:demo-float-avatar image
文字首單詞首字母特殊處理
demo-float-a fun drop-cap effect
頁面佈局:一個最常見Floats問題的解決
"Floated item"的高度是不包括在容器標籤內,如果高度超出容器標籤,會出現顯示上的錯誤,這是Floats應用於頁面佈局最常見的一個問題:demo-float-floated items overflow the wrapper
解決方案有三種:
- demo-float-clearfix hack:在容器標籤偽類
::after
清除浮動,或者在容器標籤內加一個空的div
元素清除浮動也可以解決問題; - demo-float-overflow:使用
overflow
屬性建立一個BFC,但是小心overflow:hidden
、overflow:auto
可能增加了你不需要的顯示效果; - demo-float-display:flow-root:更現代的方法是使用
display:flow-root
建立一個BFC,而且不會像overflow
增加不需要的顯示效果,但是得考慮瀏覽器支不支援這個屬性;
Table layout
在許多年以前,web開發者使用table
標籤做整個頁面的佈局,將頁面內容放入table
的行和列中,這種方法的問題在於不靈活,而且語義錯誤(對於螢幕閱讀器的使用者很不友好)。之所以放入table
標籤能佈局,是因為存在描述table layout的一些列css屬性,它們是和table
這些標籤是繫結的。而直接使用這些css屬性,用於不是table
這些元素佈局,這種方法被稱為是 "using CSS tables" :demo-using css tables;
"using css tables" 被稱作是一種遺留方法(legacy method),用於整個頁面佈局,適用於不支援Flexbox和Grid的瀏覽器,但是我這裡的最佳替補還是Floats。
Positioning
Positioning的定位和前面四種不太一樣,它一般不用於建立整個頁面佈局,而是管理和微調標籤,做一個區域性位置的調整。要注意如果已經設定以下幾個position
屬性值的標籤,層級是高於Normal flow,層級可通過z-index
屬性調整。
position:relative
相對定位,做位置調整
demo-positioning-relative-left/right:這個例子不是很深動形象,但是demo糙理不糙,確實是通過設定left
、top
等屬性值去移動位置。
posision:absolute
絕對定位,做任何可彈出、可拖拽UI部件
MDN上放了這樣一個使用場景說明:
popup information boxes and control menus; rollover panels; UI features that can be dragged and dropped anywhere on the page; and so on...
postion:fixed
固定定位
demo-position-fixed:固定表頭,表頭位置始終定於頁面頂部,不隨滾動條滾動而滾動。
當然可用於任何需要固定於頁面某個位置的UI部件。
position:sticky
粘性定位
這裡有個很經典的例子: demo-sticky-a scrolling index page where different headings stick to the top of the page as they reach it ;但是在使用時得考慮瀏覽器相容問題,相容性目前堪憂。
Multicol
Multicol是Multi-columns layout的簡稱,它提供了一種在列中佈置內容的方法,類似於文字在報紙中的流動方式,使得閱讀更加友好,不用上下滾動。Multicol的定位是這一種特殊的內容展示佈局。
報紙閱讀模式
demo-multi-column layout:通過在container
塊級元素上設定column-count
或者column-width
屬性開啟Multicol:
Flexbox、Grid考慮支援舊瀏覽的最佳實踐
最初吸引我做這個話題的原因,是目前公司專案得支援IE10、IE11,現狀是專案中的佈局方法沒有Grid、鮮有Flexbox,就比較心癢癢,想搞搞明白到底能不能在支援IE10、IE11的情況使用這兩種潮流的佈局方法。所以在舊瀏覽器中的實踐重點考慮的是IE10、IE11兩位。
Flexbox: Postcss外掛Autoprefixer
瀏覽器對Flexbox的支援還是挺不錯的,IE10支援2012版語法,IE11支援的語法和現代瀏覽器一毛一樣。在IE10和IE11中使用Flexbox存在一些已知的問題,在caniuse-flexbox有說明,同時還有一個Flexbugs是一個問題的列表以及解決措施。
所以這裡的最佳實踐分兩步:
- 藉助Postcss外掛為我們自動加上字首,以支援IE10的2012版語法和現代語法;
- 使用過程避開在IE10和IE11中使用Flexbox的已知問題,如果還是碰到了在舊瀏覽器和現代瀏覽器中表現不一致,去Flexbugs 找找有沒有相同情況。如果再沒有,再考慮替換方案,也可以給 Flexbugs 這個專案提issue;
另外貼兩篇Postcss掃盲文章:Some things you may think about PostCSS... and you might be wrong、It's Time for Everyone to Learn About PostCSSWhat It Really Is; What It Really Does
Grid: Feature Queries
瀏覽器對Grid的支援較Flexbox要差很多,IE10、IE11支援的是舊版本的規範,是帶有-ms-
字首,但即使使用autoprefixer補上了字首,相同屬性名相同屬性值在頁面中的表現也可能不一致。這樣我是不推薦Flexbox實踐中的方法,而是使用Feature Queries。
Feature Queries是使用css的@supports
,@supports
用於檢測瀏覽器是否支援引數中的屬性屬性值,如果支援則渲染花括號中的css程式碼,類似於:
@supports (display: grid) {
// code that will only run if CSS Grid is supported by the browser
}
複製程式碼
這裡有個細節點,IE10、IE11是不支援@supports
規則,所以壓根不會進入這個條件判斷,花括號中的css程式碼是不會渲染的,這與我們考慮的邏輯:支援@supports
規則、不支援display:grid
是不同的,但是最後的結果是一樣的。
以一個例子講述一下整個流程:demo-creating fallbacks in CSS
- 首先是給舊瀏覽器做支援,準備一套Fallback method,保證在所有瀏覽器上都是工作的:
.wrapper{
overflow:auto;
}
.item {
float:left;
width:33.3%;
}
複製程式碼
- 再給支援Grid的瀏覽器做覆蓋,覆蓋程式碼分兩部分,一部分是直接放入對舊瀏覽沒有影響的:
.wrapper {
display: grid;
grid-template-columns: 1fr 1fr 1fr;
}
複製程式碼
因為舊瀏覽器不支援Grid佈局,Grid相關屬性舊瀏覽器都無法解釋。在支援的瀏覽器中使得item由floated item轉為grid item,這樣的覆蓋行為由css規定,更多覆蓋情況見Fallback method。另一部分是直接放入對舊瀏覽器是有影響的,要做Feature queries:
@supports (display: grid) {
.item {
width: auto;
}
}
複製程式碼
覆蓋原有的width:33.3%
。
沒錯,這裡的實踐得寫兩套樣式。所以有人提出問題,寫一套支援所有瀏覽器的不就得了,幹嘛非得用Grid?這是個很實際的問題,畢竟寫兩套,再加測試除錯,會增加一定工作量。有幾個場景建議使用Grid:
- 專案得支援IE10、IE11等舊瀏覽器,但是開發者想嚐鮮Grid佈局,Feature Queries提供了這樣的能力;
- 專案週期會很長,可能現在不支援Grid佈局的瀏覽器,以後就支援了;
- 要實現的效果不使用Grid佈局很難實現,且對在舊瀏覽器中訪問效果要求不高,能看就行;
測試
尤其是支援IE10、IE11的專案,測試是很重要的一個環節,最佳的測試還是在各個瀏覽器中開啟。但這裡存在獲取瀏覽器的問題,例如win10系統上僅有IE11,而不能使用IE10等。有些公司有自己的伺服器,有各種瀏覽器可供測試;如果沒有的話,可以考慮下載虛擬機器:download the Virtual Machines offered by Microsoft ,或者使用像 BrowserStack 訪問遠端的虛擬機器。
從開發者角度,整個工作流程應該是這樣子:
- 初始開發計劃制定
- 開發
- 測試、發現問題
- 修復問題,重複2~4步驟
總結
- 做css佈局
- 佈局的出發點是語義化標籤
- 考慮在具體場景下使用什麼佈局方法最合適最簡單
- 考慮要不要支援舊瀏覽器,要明確支援不意味著顯示一模一樣,可存在體驗優秀+體驗一般兩種模式
- Flexbox、Grid考慮舊瀏覽器的實踐(支援IE10、IE11)
- Flexbox支援性比Grid好,使用Autoprefixer字首,避開Flexbox bug、已知issues,放開了使用
- Grid佈局要想使用,得用Feature Queries的方法,額外準備一套Fallback Methods
- Autoprefixer關閉對Grid屬性新增字首(預設行為)
- 測試
- 測試流程:初始開發計劃制定 > 開發 > 測試、發現問題 > 修復問題,重複2~4步驟
- 藉助虛擬機器等
參考連結
CSS Grid Layout and Progressive Enhancement
Using CSS Grid: Supporting Browsers Without Grid
Some things you may think about PostCSS... and you might be wrong
It's Time for Everyone to Learn About PostCSSWhat It Really Is; What It Really Does