閱讀本文之前最好對flex佈局有基本瞭解,可以通過“參考資料”中列舉的資源來學習。
flex佈局規範的設計目標
-
一維佈局模型(one-dimensional layout model),元素項沿着水平或垂直方向來排列,就像一條沿着一個方向的“流”。
與之對應的,CSS Grid Layout是一個二維佈局模型。兩者互爲補充。
- 空間分配(space distribution),(假設主軸是水平方向)元素項的最終寬度受到當前行剩餘空間(或不足空間)的影響,就像是有彈性一樣會膨脹和收縮。
- 強大的對齊支持(align and justify),align和justify本質上來說,是定義多餘(空白)的空間要放在哪裏。align(align-items, align-content)定義了交叉軸方向上的多餘(空白)空間分佈,而justify(justify-content)定義了主軸方向的多餘(空白)空間要分佈。
爲了方便討論,我們假設主軸是水平方向,當主軸是垂直方向的時候是同理的。
對align和justify的思考
主軸方向的多餘空間
justify-content
定義的是主軸方向的多餘空間要如何分佈。
主軸方向的多餘空間的出現是因爲容器寬度 > 元素項寬度之和。如圖:
這個可交互實例來自MDN。
等一下,不是說 【主軸方向的多餘空間會分配給元素項->使元素項膨脹->元素項佔滿主軸的空間】 的嗎?爲什麼這裏又有多餘的空間來給justify-content
分發呢?這是因爲元素項不一定會膨脹(flex-frow
的默認值爲0,默認不膨脹),即使膨脹,膨脹後的寬度也會受到max-width
的約束。因此有很多時候,主軸在元素項膨脹以後還是有多餘空間的。
一個行內,交叉軸方向的多餘空間
align-items
定義的是一個行內,交叉軸方向的多餘空間要如何分佈。
一個行內,交叉軸方向的多餘空間的出現是因爲行的高度大於項的高度。由於各個項的高度不一致,比較高的項會將整行的高度撐開,對於那些比較矮的項,在它的垂直方向上就會出現多餘空間。如下圖:
這個可交互實例來自MDN。關於高度撐開的討論,見用css控制元素高度:自底向上和自頂向下的方法。
行與行之間,交叉軸方向的多餘空間
align-content
定義的是行與行之間,交叉軸方向的多餘空間要如何分佈。
在這裏說的“行”,指的是一個flex容器內,由於flex-wrap: wrap造成的換行(下面會討論到換行),而不是指【第一個flex容器是一行,第二個flex容器是第二行】!
行與行之間,交叉軸方向的多餘空間的出現是因爲容器高度 > 容器內各行的高度之和。
前面說過,一個行的高度是由這一行中最高的項撐開的。一個flex容器,默認的時候(height:auto),其高度也是被其內部的所有行的高度撐開的,在這個時候容器的高度恰好等於所有行的高度之和,不存在“行與行之間,交叉軸方向的多餘空間”。
但是如果容器本身定義了height: 10000px呢?它的高度就固定了,不會受到其內部的行的影響。這時候,如果所有行的高度之和不足以填滿容器高度,交叉軸方向就會出現多餘空間。如圖:
這個可交互實例來自MDN。
對空間分配的思考
flex是如何計算項的寬度的?
所有項先按照原始寬度在容器中排列。
原始寬度由flex-basis決定,由於flex-basis默認值是auto(意思是取content-box的寬度作爲flex-basis),因此一般width就是原始寬度。如果沒有定義width,則width由項的子元素撐開。
如果容器有多餘的寬度,則將這些多餘寬度分配給每個項(分配比例由flex-grow控制),使得項的寬度增加,得到每個項的flex寬度。最終寬度基於flex寬度,但還會受到min-width、max-width的限制。
如果所有項的原始寬度已經超過了容器元素的寬度,那麼會先檢查flex-wrap
是否允許換行,如果允許換行,則換行以後再計算flex寬度;如果不允許換行,則將超出的寬度分配給每個項(分配比例由flex-shrink控制),使得項的寬度減小,得到每個子元素的flex寬度。最終寬度基於flex寬度,但還會受到min-width、max-width的限制。
總結來說就是,width決定原始寬度,flex-grow/flex-shrink決定分配比例,min-width、max-width限制最終寬度。
flex寬度計算的例子(可在瀏覽器中打開):
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<div class="container">
<div class="flex">
<div class="content1">
<div class="inner1"></div>
</div>
<div class="content2">
<div class="inner2"></div>
</div>
</div>
</div>
</body>
<style>
.flex {
display: flex;
width: 1000px;
/* 容器元素的剩餘剩餘寬度將被分配 */
}
.content1 {
flex-grow: 1;
height: 100px;
background-color: chocolate;
/* 沒有定義width,則由它的子元素撐開 */
}
.content2 {
flex-grow: 1;
height: 100px;
background-color: aqua;
/* 沒有定義width,則由它的子元素撐開 */
}
.inner1 {
height: 50px;
background-color: cornflowerblue;
/* 將父元素的寬度撐開爲100px */
width: 100px;
}
.inner2 {
height: 50px;
background-color: greenyellow;
/* width設置爲百分比時,無法撐開父元素,因此父元素的原始寬度爲0 */
width: 100%;
}
</style>
</html>
計算過程:
content1原始寬度100px,content2原始寬度0px。剩餘寬度爲1000px-100px=900px。
由於content的flex-grow都相等,因此剩餘寬度被平均分配,每個content分到450px。
content1最終寬度100px+450px=550px,content2最終寬度0px+450px=450px。
佈局的一般流程
-
根據UI設計,確定需要多少“行”來顯示所有內容,然後確定每一“行”有哪些“項”。一個“項”本身也可以成爲容器,包含一行或多行。
這裏的“行”指的就是一個
flex-direction:row
的容器了(與之前的討論不同)。它可以設置flex-wrap:wrap
,使得一個“行”容器在寬度不夠容納子元素的時候,在容器內部產生換行。 - 對每一“行”,定義其樣式。行是一個flex容器(display:flex)。並使用justify-content、align-items來定義元素在容器中的分佈方式。通過margin-top來定義行之間的縱向距離。
- 對每一“項”,定義其樣式。使用margin、padding來對元素位置進行微調。合理使用flex-grow、flex-shrink、width、min-width、max-width來調整元素的寬度。通過margin-left來定義元素之間的橫向距離。如果這“項”本身也是一個容器(包含一行或多行),返回第2步。