深入理解css3彈性盒佈局模型
引入彈性盒佈局模型的目的是提供一種更加有效的方式來對一個容器中的條目進行排列、 對齊和分配空白空間。即便容器中條目的尺寸未知或是動態變化的,彈性盒佈局模型也能正常的工作。在該佈局模型中,容器會根據佈局的需要,調整其中包含的條 目的尺寸和順序來最好地填充所有可用的空間。當容器的尺寸由於螢幕大小或視窗尺寸發生變化時,其中包含的條目也會被動態地調整。比如當容器尺寸變大時,其 中包含的條目會被拉伸以佔滿多餘的空白空間;當容器尺寸變小時,條目會被縮小以防止超出容器的範圍。彈性盒佈局是與方向無關的。在傳統的佈局方式 中,block 佈局是把塊在垂直方向從上到下依次排列的;而 inline 佈局則是在水平方向來排列。彈性盒佈局並沒有這樣內在的方向限制,可以由開發人員自由操作。
在深入到彈性盒佈局模型的細節之前,首先了解幾個相關的重要概念,具體如圖 1所示。
圖 1. 彈性盒佈局模型相關的概念
彈性盒佈局的容器(flex container)指的是採用了彈性盒佈局的 DOM 元素,而彈性盒佈局的條目(flex item)指的是容器中包含的子 DOM 元素。圖中的最外圍的邊框表示的是容器,而編號 1 和 2 的邊框表示的是容器中的條目。
從 圖中可以看到,彈性盒佈局中有兩個互相垂直的座標軸:一個稱之為主軸(main axis),另外一個稱之為交叉軸(cross axis)。主軸並不固定為水平方向的 X 軸,交叉軸也不固定為垂直方向的 Y 軸。在使用時,通過 CSS 屬性宣告首先定義主軸的方向(水平或垂直),則交叉軸的方向也相應確定下來。容器中的條目可以排列成單行或多行。主軸確定了容器中每一行上條目的排列方 向,而交叉軸則確定行本身的排列方向。可以根據不同的頁面設計要求來確定合適的主軸方向。有些容器中的條目要求從左到右水平排列,則主軸應該是水平方向 的;而另外一些容器中的條目要求從上到下垂直排列,則主軸應該是垂直方向的。
確定主軸和交叉軸的方向之後,還需要確定它們各自的排列方向。 對於水平方向上的軸,可以從左到右或從右到左來排列;對於垂直方向上的軸,則可以從上到下或從下到上來排列。對於主軸來說,排列條目的起始和結束位置分別 稱為主軸起始(main start)和主軸結束(main end);對於交叉軸來說,排列行的起始和結束位置分別稱為交叉軸起始(cross start)和交叉軸結束(cross end)。在容器進行佈局時,在每一行中會把其中的條目從主軸起始位置開始,依次排列到主軸結束位置;而當容器中存在多行時,會把每一行從交叉軸起始位置 開始,依次排列到交叉軸結束位置。
彈性盒佈局中的條目有兩個尺寸:主軸尺寸和交叉軸尺寸,分別對應其 DOM 元素在主軸和交叉軸上的大小。如果主軸是水平方向,則主軸尺寸和交叉軸尺寸分別對應於 DOM 元素的寬度和高度;如果主軸是垂直方向,則兩個尺寸要反過來。與主軸和交叉軸尺寸對應的是主軸尺寸屬性和交叉軸尺寸屬性,指的是 CSS 中的屬性 width 或 height。比如,當主軸是水平方向時,主軸尺寸屬性是 width,而 width 的值是主軸尺寸的大小。
彈性盒布 局模型中的 CSS 樣式宣告分別適用於容器或條目。在下面的內容中會詳細的介紹相關的 CSS 屬性。首先介紹如何使用彈性盒佈局模型進行基本的頁面佈局。在本文的所有程式碼示例中,容器的 CSS 類名統一為 flex-container,而條目的 CSS 類名則為 flex-item。所有的示例都可以在CodePen上進行預覽。
基本佈局
首先從最基本的佈局開始介紹彈性盒佈局模型。要實現的佈局效果是一個簡單的圖片縮圖預覽頁面。頁面的基本 HTML 如程式碼清單 1所示。
清單 1. 簡單的圖片縮圖預覽頁面的 HTML 程式碼
<ul class="flex-container"> <li class="flex-item"><img src="http://placehold.it/300&text=1"></li> <li class="flex-item"><img src="http://placehold.it/300&text=2"></li> <li class="flex-item"><img src="http://placehold.it/300&text=3"></li> <li class="flex-item"><img src="http://placehold.it/300&text=4"></li> <li class="flex-item"><img src="http://placehold.it/300&text=5"></li> <li class="flex-item"><img src="http://placehold.it/300&text=6"></li> </ul>
該頁面的基本 HTML 是很簡單的。在一個<ul>元素下面有 6 個<li>元素。每個<li>元素中包含一個大小為 300x300 的縮圖圖片。<ul>元素作為彈性盒佈局的容器,而<li>元素則是容器中的條目。實現基本佈局的 CSS 如程式碼清單 2所示。
清單 2. 簡單的圖片縮圖預覽頁面的 CSS 程式碼
.flex-container { list-style: none; display: flex; flex-direction: row; flex-wrap: wrap; } .flex-item { padding: 5px; }
在程式碼清單 2 中,對於彈性盒佈局的容器,使用"display: flex"宣告使用彈性盒佈局。CSS 屬性宣告"flex-direction"用來確定主軸的方向,從而確定基本的條目排列方式。"flex-direction"屬性的可選值及其含義如表1所示。
表 1. “flex-direction”屬性的可選值及其含義
屬性值 | 含義 |
---|---|
row(預設值) | 主軸為水平方向。排列順序與頁面的文件順序相同。如果文件順序是 ltr,則排列順序是從左到右;如果文件順序是 rtl,則排列順序是從右到左。 |
row-reverse | 主軸為水平方向。排列順序與頁面的文件順序相反。 |
column | 主軸為垂直方向。排列順序為從上到下。 |
column-reverse | 主軸為垂直方向。排列順序為從下到上。 |
預設情況下,彈性盒容器中的條目會盡量佔滿容器在主軸方向上的一行。當容器的主軸尺寸小於其 所有條目總的主軸尺寸時,會出現條目之間互相重疊或超出容器範圍的現象。CSS 屬性"flex-wrap"用來聲明當容器中條目的尺寸超過主軸尺寸時應採取的行為。"flex-wrap"屬性的可選值及其含義如表2所示。
表 2. “flex-wrap”屬性的可選值及其含義
屬性值 | 含義 |
---|---|
nowrap(預設值) | 容器中的條目只佔滿容器在主軸方向上的一行,可能出現條目互相重疊或超出容器範圍的現象。 |
wrap | 當容器中的條目超出容器在主軸方向上的一行時,會把條目排列到下一行。下一行的位置與交叉軸的方向一致。 |
wrap-reverse | 與 wrap 的含義類似,不同的是下一行的位置與交叉軸的方向相反。 |
可以使用"flex-flow"屬性把"flex-direction"和"flex-wrap"結合起來,如程式碼清單 3所示。
清單 3. “flex-flow”屬性的使用示例
flex-flow: row wrap;
容器中的條目
除了彈性盒佈局模型中的容器之外,容器中的條目也可以通過 CSS 屬性來改變其佈局行為。下面介紹容器中的條目可以使用的 CSS 屬性宣告。
條目的順序
預設情況下,容器中條目的順序取決於它們在 HTML 標記程式碼中的出現順序。可以通過"order"屬性來改變條目在容器中的出現順序。對於程式碼中的 HTML 標記新增程式碼清單 4中的 CSS 宣告,執行之後的效果是最後一個<li>元素出現在了其他<li>元素的最前面。預覽的頁面見這裡。
清單 4. “order”屬性的使用示例
.flex-item:last-child { order: -1; }
"order"屬性的主要作用是兼顧頁面的樣式和可訪問性。支援可訪問性的裝置,如螢幕閱讀器,都是按照 HTML 中程式碼的順序來讀取元素的。這就要求一些相對重要的文字出現在 HTML 標記的前面。而對於使用瀏覽器的一般使用者來說,在某些情況下把一些相對不重要的圖片顯示在前面是更好的選擇。比如在一個商品的展示頁面中,在原始碼中把商 品描述的文字放在商品圖片之前。這樣可以方便螢幕閱讀器的讀取;而在 CSS 中使用"order"屬性把圖片放在文字的前面。這樣可以讓使用者首先看到圖片。
條目尺寸的彈性
彈 性盒佈局模型的核心在於容器中條目的尺寸是彈性的。容器可以根據本身尺寸的大小來動態地調整條目的尺寸。當容器中有空白空間時,條目可以擴充套件尺寸以佔據額 外的空白空間;當容器中的空間不足時,條目可以縮小尺寸以防止超出容器範圍。條目尺寸的彈性由 3 個 CSS 屬性來確定,分別是"flex-basis"、"flex-grow"和"flex-shrink"。
"flex-basis"屬性宣告接 受的值與"width"屬性是一樣的,用來確定彈性條目的初始主軸尺寸。這是條目的尺寸被容器調整之前的初始值。如果"flex-basis"的值為 auto,則實際使用的值是主軸尺寸屬性的值,即 width 或 height 屬性的值。如果主軸尺寸屬性的值也是 auto,則使用的值由條目內容的尺寸來確定。
"flex-grow"屬性的值是一個沒有單位的非負數,預設值是 1。"flex-grow"屬性的值表示的是當容器有多餘的空間時,這些空間在不同條目之間的分配比例。比如,一個容器中有 3 個條目,其"flex-grow"屬性的值分別為 1,2 和 3,那麼當容器中有空白空間時,這 3 個條目所獲得的額外空白空間分別佔全部空間的 1/6、1/3 和 1/2,如程式碼清單 5所示。預覽的頁面見這裡。
清單 5. “flex-grow”屬性的使用示例
.flex-item:nth-child(1) { flex-grow: 1; } .flex-item:nth-child(2) { flex-grow: 2; } .flex-item:nth-child(3) { flex-grow: 3; }
"flex-shrink"屬性在使用上類似於"flex-grow"。該屬性的值也是無單位的,表示的是當容器的空間 不足時,各個條目的尺寸縮小的比例。在進行尺寸縮小時,條目的縮小比例與"flex-basis"的值相乘之後,就得到了應該縮小的尺寸的實際值。例如, 在容器中有 3 個條目,其"flex-shrink"屬性的值分別為 1,2 和 3。每個條目的主軸尺寸均為 200px。當容器的主軸尺寸不足 600px 時,比如變成了 540px 之後, 則需要縮小的尺寸 60px 由 3 個條目按照比例來分配。3 個條目分別要縮小 10px、20px 和 30px,主軸尺寸分別變為 190px、180px 和 170px。預覽的頁面見這裡。
使 用屬性"flex"可以同時宣告"flex-basis"、"flex-grow"和"flex-shrink"的值,格式是"none | [ <'flex-grow'> <'flex-shrink'>? || <'flex-basis'> ]"。該屬性的值的 3 個組成部分的初始值分別是"0 1 auto"。當屬性"flex"的值為 none 時,相當於"0 0 auto"。當組成部分"flex-basis"被省略時,其值為 0%。程式碼清單 6給出了屬性"flex"的使用示例。
清單 6. 屬性“flex”的使用示例
.flex-item:nth-child(1) { flex: 1 1 auto; } flex: 1; // flex-grow 的值為 1,flex-shrink 的值為 1,flex-basis 的值為 0%。
需要注意的是,在容器分配額外空間時是以"行"為單位的。容器先根據"flex-wrap"的屬性值來確定是單行佈局或多行佈局,然後把條目分配到對應的行中,最後在每一行內進行空白空間的分配。如程式碼清單 7中的 CSS 宣告示例,在容器中有 4 個條目。
清單 7. 多行佈局的 CSS 宣告
.flex-container { width: 990px; } .flex-item { width: 300px; flex: auto; }
由於容器的寬度只有 990px,所以在一行中只能排列 3 個條目,而剩下的 1 個條目則會被排列到單獨的一行中。在 3 個條目的行中,多餘的空間 90px 被平均分配給 3 個條目;而在一個條目單獨的行中,多餘的 690px 被該條目完全佔據。
條目對齊
當容器中的條目的尺寸確定之後,可以設定這些條目在容器中的對齊方式。對齊條目的方式有 3 種。
第一種方式是使用自動空白邊,即"margin: auto"。在使用自動空白邊時,容器中額外的空白空間會被宣告為 auto 的空白邊佔據,如程式碼清單 8所示。CSS 宣告 profile 中通過"margin-left: auto"使得該條目左邊的空白邊會佔據額外的空間,從而"Profile"文字會靠右顯示。預覽的頁面見這裡。
清單 8. 使用自動空白邊進行條目對齊
<div class="flex-container"> <div class="logo">Logo</div> <div class="profile">Profile</div> </div> .flex-container { display: flex; } .profile { margin-left: auto; }
第二種方式是在主軸方向上的對齊。這是通過容器上的"justify-content"屬性來進行設定,可以調整條目在 主軸方向上的對齊方式。這種條目對齊方式的調整發生在修改條目的彈性尺寸和處理自動空白邊之後。當容器中的一行中的條目沒有彈性尺寸,或是已經達到了它們 的最大尺寸時,在這一行上可能還有額外的空白空間。使用"justify-content"屬性可以分配這些空間。該屬性還可以控制當條目超出行的範圍時 的對齊方式。"justify-content"屬性的可選值和含義如表3所示,實際的佈局效果見圖 2。
表 3. “justify-content”屬性的可選值和含義
屬性值 | 含義 |
---|---|
flex-start | 條目集中於該行的起始位置。第一個條目與其所在行在主軸起始方向上的邊界保持對齊,其餘的條目按照順序依次排列。 |
flex-end | 條目集中於該行的結束方向。最後一個條目與其所在行在主軸結束方向上的邊界保持對齊,其餘的條目按照順序依次排列。 |
center | 條目集中於該行的中央。條目都往該行的中央排列,在主軸起始方向和結束方向上留有同樣大小的空白空間。如果空白空間不足,則條目會在兩個方向上超出同樣的空間。 |
space-between | 第一個條目與其所在行在主軸起始方向上的邊界保持對齊,最後一個條目與其所在行在主軸結束方向上的邊界保持對齊。空白空間在條目之間平均分配,使得相鄰條目之間的空白尺寸相同。 |
space-around | 類似於 space-between,不同的是第一個條目和最後一個條目與該行的邊界之間同樣存在空白空間,該空白空間的尺寸是條目之間的空白空間的尺寸的一半。 |
圖 2. “justify-content”屬性不同值的佈局效果
第 三種方式是交叉軸方向上的對齊。除了在主軸方向上對齊之外,條目也可以在交叉軸方向上對齊。容器上的屬性"align-items"用來設定容器中所有條 目在交叉軸上的預設對齊方向,而條目上的屬性"align-self"用來覆寫容器指定的對齊方式。屬性"align-items"的可選值和含義如表4所示,實際的佈局效果見圖 3。
表 4. 屬性“align-items”的可選值和含義
屬性值 | 含義 |
---|---|
flex-start | 條目與其所在行在交叉軸起始方向上的邊界保持對齊。 |
flex-end | 條目與其所在行在交叉軸結束方向上的邊界保持對齊。 |
center | 條目的空白邊盒子(margin box)在交叉軸上居中。如果交叉軸尺寸小於條目的尺寸,則條目會在兩個方向上超出相同大小的空間。 |
baseline | 條目在基準線上保持對齊。在所有條目中,基準線與交叉軸起始方向上的邊界距離最大的條目,它與所在行在交叉軸方向上的邊界保持對齊。 |
stretch | 如果條目的交叉軸尺寸的計算值是"auto",則其實際使用的值會使得條目在交叉軸方向上儘可能地佔滿。 |
屬性"align-self"的可選值除了表中列出的之外,還可以設定為"auto"。當"align-self"的值為 auto 時,其計算值是父節點的屬性"align-items"的值。如果該節點沒有父節點,則計算值為"stretch"。
圖 3. 屬性“align-items”不同值的佈局效果
交叉軸空白處理
當 容器在交叉軸方向上有空白空間時,屬性"align-content"用來對齊容器中的行。該屬性的作用類似於"justify-content",只不 過"justify-content"是在主軸方向上對齊行中的條目。當容器中只有單行時,該屬性不起作用。屬性"align-content"的可選值 和含義如表5所示,實際的佈局效果見圖 4。
表 5. 屬性“align-content”的可選值和含義
屬性值 | 含義 |
---|---|
flex-start | 行集中於容器的交叉軸起始位置。第一行與容器在交叉軸起始方向上的邊界保持對齊,其餘行按照順序依次排列。 |
flex-end | 行集中於容器的交叉軸結束位置。第一行與容器在交叉軸結束方向上的邊界保持對齊,其餘行按照順序依次排列。 |
center | 行集中於容器的中央。行都往容器的中央排列,在交叉軸起始方向和結束方向上留有同樣大小的空白空間。如果空白空間不足,則行會在兩個方向上超出同樣的空間。 |
space-between | 行在容器中均勻分佈。第一行與容器在交叉軸起始方向上的邊界保持對齊,最後一行與容器在交叉軸結束方向上的邊界保持對齊。空白空間在行之間平均分配,使得相鄰行之間的空白尺寸相同。 |
space-around | 類似於 space-between,不同的是第一行條目和最後一個行目與容器行的邊界之間同樣存在空白空間,而該空白空間的尺寸是行目之間的空白空間的尺寸的一半。 |
stretch | 伸展行來佔滿剩餘的空間。多餘的空間在行之間平均分配,使得每一行的交叉軸尺寸變大。 |
圖 4. 屬性“align-content”的不同值的佈局效果
應用示例
下面通過一個示例來具體說明彈性盒佈局在實際開發中的應用。該示例是一個部落格帖子的典型頁面佈局。在展示一個部落格帖子時,頁面上通常包括標題、發表者、日期和時間、評論數量、正文、插圖、評論列表等元素。這些元素基本上按照從上到下的順序依次排列。程式碼清單 9和清單 10中給出了示例的 HTML 和 CSS 程式碼。預覽頁面見這裡。
清單 9. 部落格頁面的 HTML 程式碼
<div class="post"> <h1>This is my first blog post</h1> <div class="post-meta"> <div class="author">Alex Cheng</div> <div class="datetime">2014-07-02 10:10 am</div> <div class="comments-count">2 comments</div> </div> <div class="post-body"> My first blog post. </div> <div class="post-image"> <img src="http://placehold.it/500x200&text=1"> </div> <div class="post-comments"> <h3>Comments</h3> <ul> <li><div class="author">Bob</div><div>This is a good post.</div></li> <li><div class="autho">David</div><div>Good post.</div></li> </ul> </div> </div>
清單 10. 部落格頁面的 CSS 程式碼
.post { display: flex; flex-flow: column wrap; } .post-meta { display: flex; flex-flow: row wrap; order: 1; } .post-body { order: 3; } .post-comments { order: 4; } .comments-count { margin-left: auto; } .post-image { order: 2; align-self: center; }
該示例中主要使用了"order"屬性來改變條目的顯示位置,以及使用"align-self"來使得圖片居中顯示。
應用示例
下面通過一個示例來具體說明彈性盒佈局在實際開發中的應用。該示例是一個部落格帖子的典型頁面佈局。在展示一個部落格帖子時,頁面上通常包括標題、發表者、日期和時間、評論數量、正文、插圖、評論列表等元素。這些元素基本上按照從上到下的順序依次排列。程式碼清單 9和清單 10中給出了示例的 HTML 和 CSS 程式碼。預覽頁面見這裡。
清單 9. 部落格頁面的 HTML 程式碼
<div class="post"> <h1>This is my first blog post</h1> <div class="post-meta"> <div class="author">Alex Cheng</div> <div class="datetime">2014-07-02 10:10 am</div> <div class="comments-count">2 comments</div> </div> <div class="post-body"> My first blog post. </div> <div class="post-image"> <img src="http://placehold.it/500x200&text=1"> </div> <div class="post-comments"> <h3>Comments</h3> <ul> <li><div class="author">Bob</div><div>This is a good post.</div></li> <li><div class="autho">David</div><div>Good post.</div></li> </ul> </div> </div>
清單 10. 部落格頁面的 CSS 程式碼
.post { display: flex; flex-flow: column wrap; } .post-meta { display: flex; flex-flow: row wrap; order: 1; } .post-body { order: 3; } .post-comments { order: 4; } .comments-count { margin-left: auto; } .post-image { order: 2; align-self: center; }
該示例中主要使用了"order"屬性來改變條目的顯示位置,以及使用"align-self"來使得圖片居中顯示。
總結
作為 CSS3 規範的一部分,彈性盒佈局模型可以在很多典型的場景中簡化完成佈局所需的 CSS 程式碼。該佈局模型也提供了很多實用的特性來滿足常見的佈局要求,包括對容器中條目的排列、對齊、調整大小和分配空白空間等。彈性盒佈局模型可以作為 Web 開發人員工具箱中的一個很好的工具。