1. 程式人生 > >深入理解 CSS3 彈性盒布局模型

深入理解 CSS3 彈性盒布局模型

分辨率 top 應用 時間 控制 用戶 lock fire 應用開發

彈性盒布局模型(Flexible Box Layout)是 CSS3 規範中提出的一種新的布局方式。該布局模型的目的是提供一種更加高效的方式來對容器中的條目進行布局、對齊和分配空間。這種布局方式在條目尺寸未知或動態時也能工作。這種布局方式已經被主流瀏覽器所支持,可以在 Web 應用開發中使用。本文詳細的介紹該布局模型以及如何在具體開發中應用該布局模型來簡化常見的頁面布局場景。

Web 應用的樣式設計中,布局是非常重要的一部分。布局用來確定頁面上不同組件和元素的尺寸和位置。隨著響應式用戶界面的流行,Web 應用一般都要求適配不同的設備尺寸和瀏覽器分辨率。響應式用戶界面設計中最重要的一環就是布局。需要根據窗口尺寸來調整布局,從而改變組件的尺寸和位置,以達到最佳的顯示效果。這也使得布局的邏輯變得更加復雜。本文介紹的是 CSS3 規範中引入的新布局模型:彈性盒模型(flex box)。彈性盒模型可以用簡單的方式滿足很多常見的復雜的布局需求。它的優勢在於開發人員只是聲明布局應該具有的行為,而不需要給出具體的實現方式。瀏覽器會負責完成實際的布局。該布局模型在主流瀏覽器中都得到了支持。

簡介

引入彈性盒布局模型的目的是提供一種更加有效的方式來對一個容器中的條目進行排列、對齊和分配空白空間。即便容器中條目的尺寸未知或是動態變化的,彈性盒布局模型也能正常的工作。在該布局模型中,容器會根據布局的需要,調整其中包含的條目的尺寸和順序來最好地填充所有可用的空間。當容器的尺寸由於屏幕大小或窗口尺寸發生變化時,其中包含的條目也會被動態地調整。比如當容器尺寸變大時,其中包含的條目會被拉伸以占滿多余的空白空間;當容器尺寸變小時,條目會被縮小以防止超出容器的範圍。彈性盒布局是與方向無關的。在傳統的布局方式中,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="//placehold.it/300&text=1"></li>
   <li class="flex-item"><img src="//placehold.it/300&text=2"></li>
   <li class="flex-item"><img src="//placehold.it/300&text=3"></li>
   <li class="flex-item"><img src="//placehold.it/300&text=4"></li>
   <li class="flex-item"><img src="//placehold.it/300&text=5"></li>
   <li class="flex-item"><img src="//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="//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"來使得圖片居中顯示。


瀏覽器支持

由於彈性盒模型規範本身有過多個不同的版本,因此瀏覽器對於該規範的支持也存在一些不一致。瀏覽器一共支持 3 個不同版本規範的語法:

  • 新規範:最新版本規範的語法,即"display: flex"。
  • 中間版本:2011 年的非官方規範的語法,即"display: flexbox"。
  • 老規範:2009 年的規範的語法,即"display: box"。

瀏覽器的支持情況如下表6所示。

表 6. 彈性盒布局模型的瀏覽器支持
ChromeSafariFirefoxOperaIEAndroidiOS
21+(新規範)
20-(老規範)
6.1+(新規範)
3.1+(老規範)
22+(新規範)
2-21(老規範)
12.1+(新規範) 11+(新規範)
10(中間版本)
4.4+(新規範)
2.1+(老規範)
7.1+(新規範)
3.2+(老規範)

從表6中可以看到,彈性盒布局模型已經被主流的瀏覽器所支持。不過為了支持不同版本的瀏覽器,在使用時除了規範中定義的屬性之外,還需要添加相應的瀏覽器前綴形式,如代碼清單 11所示。

清單 11. 彈性盒布局模型的瀏覽器前綴形式
.flex-container {
 display: -webkit-box;
 display: -moz-box;
 display: -ms-flexbox;
 display: -webkit-flex;
 display: flex;
}

.flex-item {
 -webkit-box-flex: auto;

-moz-box-flex: auto;
-webkit-flex: auto;
-ms-flex: auto;
 flex: auto;
}

.flex-item {
 -webkit-box-ordinal-group: 1;
 -moz-box-ordinal-group: 1;
 -ms-flex-order: 1;
 -webkit-order: 1;
 order: 1;
}

對於這些瀏覽器相關的前綴,最好使用 autoprefixer 這樣的工具來進行處理。


總結

作為 CSS3 規範的一部分,彈性盒布局模型可以在很多典型的場景中簡化完成布局所需的 CSS 代碼。該布局模型也提供了很多實用的特性來滿足常見的布局要求,包括對容器中條目的排列、對齊、調整大小和分配空白空間等。彈性盒布局模型可以作為 Web 開發人員工具箱中的一個很好的工具。

深入理解 CSS3 彈性盒布局模型