一篇文章帶你掌握Flex佈局的所有用法

2023-02-07 21:00:42

Flex 佈局目前已經非常流行了,現在幾乎已經相容所有瀏覽器了。在文章開始之前我們需要思考一個問題:我們為什麼要使用 Flex 佈局?

其實答案很簡單,那就是 Flex 佈局好用。一個新事物的出現往往是因為舊事物不那麼好用了,比如,如果想讓你用傳統的 css 佈局來實現一個塊元素垂直水平居中你會怎麼做?實現水平居中很簡單,margin: 0 auto就行,而實現垂直水平居中則可以使用定位實現:

<div class="container">
  <div class="item"></div>
</div>
.container {
  position: relative;
  width: 300px;
  height: 300px;
  background: red;
}
.item {
  position: absolute;
  background: black;
  width: 50px;
  height: 50px;
  margin: auto;
  left: 0;
  top: 0;
  bottom: 0;
  right: 0;
}

或者

.item {
  position: absolute;
  background: black;
  width: 50px;
  height: 50px;
  margin: auto;
  left: calc(50% - 25px);
  top: calc(50% - 25px);
}

但是這樣都顯得特別繁瑣,明明可以一個屬性就能解決的事情沒必要寫這麼麻煩。而使用 Flex 則可以使用 place-content 屬性簡單的實現(place-content 為 justify-content 和 align-content 簡寫屬性)

.container {
  width: 300px;
  height: 300px;
  background: red;
  display: flex;
  place-content: center;
}
.item {
  background: black;
  width: 50px;
  height: 50px;
}

接下來的本篇文章將會帶領大家一起來探討Flex佈局

基本概念

我們先寫一段程式碼作為範例(部分屬性省略)

html

<div class="container">
  <div class="item">flex專案</div>
  <div class="item">flex專案</div>
  <div class="item">flex專案</div>
  <div class="item">flex專案</div>
</div>
.container {
  display: flex;
  width: 800px;
  gap: 10px;
}
.item {
  color: #fff;
}

flex 容器

我們可以將一個元素的 display 屬性設定為 flex,此時這個元素則成為flex 容器比如container元素

flex 專案

flex 容器的子元素稱為flex 專案,比如item元素

flex 佈局有兩個軸,主軸交叉軸,至於哪個是主軸哪個是交叉軸則有flex 容器flex-direction屬性決定,預設為:flex-direction:row,既橫向為主軸,縱向為交叉軸,

flex-direction還可以設定其它三個屬性,分別為row-reverse,column,column-reverse

  • row-reverse

  • column

  • column-reverse

從這裡我們可以看出 Flex 軸的方向不是固定不變的,它受到flex-direction的影響

不足空間和剩餘空間

當 Flex 專案總寬度小於 Flex 容器寬度時就會出現剩餘空間

當 Flex 專案總寬度大於 Flex 容器寬度時就會出現不足空間

Flex 專案之間的間距

Flex 專案之間的間距可以直接在 Flex 容器上設定 gap 屬性即可,如

<div class="container">
  <div class="item">A</div>
  <div class="item">B</div>
  <div class="item">C</div>
  <div class="item">D</div>
</div>
.container {
  display: flex;
  width: 500px;
  height: 400px;
  gap: 10px;
}
.item {
  width: 150px;
  height: 40px;
}

Flex 屬性

flex屬性是flex-grow,flex-shrink,flex-basis三個屬性的簡寫。下面我們來看下它們分別是什麼。

  • flex-basis可以設定 Flex 專案的大小,一般主軸為水平方向的話和 width 解析方式相同,但是它不一定是 Flex 專案最終大小,Flex 專案最終大小受到flex-grow,flex-shrink以及剩餘空間等影響,後面文章會告訴大家最終大小的計算方式

  • flex-grow為 Flex 專案的擴充套件係數,當 Flex 專案總和小於 Flex 容器時就會出現剩餘空間,而flex-grow的值則可以決定這個 Flex 專案可以分到多少剩餘空間

  • flex-shrink為 Flex 專案的收縮係數,同樣的,當 Flex 專案總和大於 Flex 容器時就會出現不足空間,flex-shrink的值則可以決定這個 Flex 專案需要減去多少不足空間

既然flex屬性是這三個屬性的簡寫,那麼flex屬性簡寫方式分別代表什麼呢?

flex屬性可以為 1 個值,2 個值,3 個值,接下來我們就分別來看看它們代表什麼意思

  • 一個值

如果flex屬性只有一個值的話,我們可以看這個值是否帶單位,帶單位那就是flex-basis,不帶就是flex-grow

.item {
  flex: 1;

  /* 相當於 */
  flex-grow: 1;
  flex-shrink: 1;
  flex-basis: 0;
}
​ .item {
  flex: 30px;

  /* 相當於 */
  flex-grow: 1;
  flex-shrink: 1;
  flex-basis: 30px;
}
  • 兩個值

flex屬性有兩個值的話,第一個無單位的值就是flex-grow,第一個無單位的值則是flex-shrink,有單位的就是flex-basis

.item {
  flex: 1 2;

  /* 相當於 */
  flex-grow: 1;
  flex-shrink: 2;
  flex-basis: 0;
}
​ .item {
  flex: 30px 2;

  /* 相當於 */
  flex-grow: 2;
  flex-shrink: 1;
  flex-basis: 30px;
}
  • 三個值

flex屬性有三個值的話,第一個無單位的值就是flex-grow,第一個無單位的值則是flex-shrink,有單位的就是flex-basis

.item {
  flex: 1 2 10px;

  /* 相當於 */
  flex-grow: 1;
  flex-shrink: 2;
  flex-basis: 10px;
}
​ .item {
  flex: 30px 2 1;

  /* 相當於 */
  flex-grow: 2;
  flex-shrink: 1;
  flex-basis: 30px;
}

​ .item {
  flex: 2 30px 1;

  /* 相當於 */
  flex-grow: 2;
  flex-shrink: 1;
  flex-basis: 30px;
}

另外,flex 的值還可以為initial,auto,none

  • initial

initial 為預設值,和不設定 flex 屬性的時候表現一樣,既 Flex 專案不會擴充套件,但會收縮,Flex 專案大小有本身內容決定

​ .item {
  flex: initial;

  /* 相當於 */
  flex-grow: 0;
  flex-shrink: 1;
  flex-basis: auto;
}
  • auto

當 flex 設定為 auto 時,Flex 專案會根據自身內容確定flex-basis,既會拓展也會收縮

​ .item {
  flex: auto;

  /* 相當於 */
  flex-grow: 1;
  flex-shrink: 1;
  flex-basis: auto;
}
  • none

none 表示 Flex 專案既不收縮,也不會擴充套件

​ .item {
  flex: none;

  /* 相當於 */
  flex-grow: 0;
  flex-shrink: 0;
  flex-basis: auto;
}

Flex 專案大小的計算

首先看一下 flex-grow 的計算方式

flex-grow

面試中經常問到: 為什麼 flex 設定為 1 的時候,Flex 專案就會均分 Flex 容器? 其實 Flex 專案設定為 1 不一定會均分容器(後面會解釋),這裡我們先看下均分的情況是如何發生的

同樣的我們先舉個例子

<div class="container">
  <div class="item">Xiaoyue</div>
  <div class="item">June</div>
  <div class="item">Alice</div>
  <div class="item">Youhu</div>
  <div class="item">Liehuhu</div>
</div>
.container {
  display: flex;
  width: 800px;
}
.item {
  flex: 1;
  font-size: 30px;
}

flex 容器總寬度為 800px,flex 專案設定為flex:1,此時頁面上顯示

我們可以看到每個專案的寬度為 800/5=160,下面來解釋一下為什麼會均分:

首先

​ .item {
  flex: 1;

  /* 相當於 */
  flex-grow: 1;
  flex-shrink: 1;
  flex-basis: 0;
}

因為flex-basis為 0,所有 Flex 專案擴充套件係數都是 1,所以它們分到的剩餘空間都是一樣的。下面看一下是如何計算出最終專案大小的

這裡先給出一個公式:

Flex專案彈性量 = (Flex容器剩餘空間/所有flex-grow總和)*當前Flex專案的flex-grow

其中Flex專案彈性量指的是分配給 Flex 專案多少的剩餘空間,所以 Flex 專案的最終寬度為

flex-basis+Flex專案彈性量

根據這個公式,上面的均分也就很好理解了,因為所有的flex-basis為 0,所以剩餘空間就是 800px,每個 Flex 專案的彈性量也就是(800/1+1+1+1+1)*1=160,那麼最終寬度也就是160+0=160

剛剛說過 flex 設定為 1 時 Flex 專案並不一定會被均分,下面就來介紹一下這種情況,我們修改一下範例中的 html,將第一個 item 中換成一個長單詞

<div class="container">
  <div class="item">Xiaoyueyueyue</div>
  <div class="item">June</div>
  <div class="item">Alice</div>
  <div class="item">Youhu</div>
  <div class="item">Liehu</div>
</div>

此時會發現 Flex 容器並沒有被均分

因為計算出的靈活性 200px 小於第一個 Flex 專案的min-content(217.16px),此時瀏覽器會採用 Flex 專案的min-content作為最終寬度,而後面的 Flex 專案會在第一個 Flex 專案計算完畢後再進行同樣的計算

我們修改一下 flex,給它設定一個 flex-basis,看下它計算之後的情況

.item {
  text-align: center;
  flex: 1 100px;
}

因為每個專案的flex-basis都是 100px,Flex 容器剩餘空間800-500=300px,所以彈性量就是(300/5)*1=60px,最終寬度理論應該為100+60=160px,同樣的因為第一個 Flex 專案的min-content為 217.16px,所以第一個 Flex 專案寬度被設定為 217.16px,最終表現和上面一樣

我們再來看一下為什麼第 2,3,4,5 個 Flex 專案寬度為什麼是 145.71px

當瀏覽器計算完第一個 Flex 專案為 217.16px 後,此時的剩餘空間為800-217.16-100*4=182.84,第 2 個 Flex 專案彈性量(182.84/1+1+1+1)*1=45.71,所以最終寬度為100+45.71=145.71px,同樣的後面的 Flex 專案計算方式是一樣的,但是如果後面再遇到長單詞,假如第五個是長單詞,那麼不足空間將會發生變化,瀏覽器會將第五個 Flex 專案寬度計算完畢後再回頭進行一輪計算,具體情況這裡不再展開

所以說想要均分 Flex 容器 flex 設定為 1 並不能用在所有場景中,其實當 Flex 專案中有固定寬度元素也會出現這種情況,比如一張圖片等,當然如果你想要解決這個問題其實也很簡單,將 Flex 專案的min-width設定為 0 即可

.item {
  flex: 1 100px;
  min-width: 0;
}

flex-grow 為小數

flex-grow 的值不僅可以為正整數,還可以為小數,當為小數時也分為兩種情況:所有 Flex 專案的 flex-grow 之和小於等於 1 和大於 1,我們先看小於等於 1 的情況,將例子的改成

<div class="container">
  <div class="item">Acc</div>
  <div class="item">Bc</div>
  <div class="item">C</div>
  <div class="item">DDD</div>
  <div class="item">E</div>
</div>
.item:nth-of-type(1) {
  flex-grow: 0.1;
}
.item:nth-of-type(2) {
  flex-grow: 0.2;
}
.item:nth-of-type(3) {
  flex-grow: 0.2;
}
.item:nth-of-type(4) {
  flex-grow: 0.1;
}
.item:nth-of-type(5) {
  flex-grow: 0.1;
}

效果如圖

我們可以發現專案並沒有佔滿容器,它的每個專案的彈性量計算方式為

Flex專案彈性量=Flex容器剩餘空間*當前Flex專案的flex-grow

相應的每個專案的實際寬度也就是flex-basis+彈性量,首先先不設定 flex-grow,我們可以看到每個專案的 flex-basis 分別為: 51.2 , 33.88 , 20.08 , 68.56 , 16.5

所以我們可以計算出 Flex 容器的剩餘空間為800-51.2 -33.88 - 20.08 - 68.56 - 16.5=609.78,這樣我們就可以算出每個專案的實際尺寸為

A: 實際寬度 = 51.2 + 609.78*0.1 = 112.178

B: 實際寬度 = 33.88 + 609.78*0.2 = 155.836

...

下面看下 flex-grow 之和大於 1 的情況,將例子中的 css 改為

.item:nth-of-type(1) {
  flex-grow: 0.1;
}
.item:nth-of-type(2) {
  flex-grow: 0.2;
}
.item:nth-of-type(3) {
  flex-grow: 0.3;
}
.item:nth-of-type(4) {
  flex-grow: 0.4;
}
.item:nth-of-type(5) {
  flex-grow: 0.5;
}

此時的效果為

可以看出 Flex 專案是佔滿容器的,它的計算方式其實和 flex-grow 為正整數時一樣

Flex專案彈性量 = (Flex容器剩餘空間/所有flex-grow總和)*當前Flex專案的flex-grow

所以我們可以得出一個結論: Flex 專案的 flex-grow 之和小於 1,Flex 專案不會佔滿 Flex 容器

flex-shrink

flex-shrink 其實和 flex-grow 基本一樣,就是擴充套件變成了收縮,flex-grow 是專案比例增加容器剩餘空間,而 flex-shrink 則是比例減去容器不足空間

修改一下我們的例子:

.item {
  flex-basis: 200px;
  /* 相當於 */
  flex-shrink: 1;
  flex-grow: 0;
  flex-basis: 200px;
}

此時專案的總寬度200*5=1000px已經大於容器總寬度800px,此時計算第一個專案的不足空間就是800-200*5=-200px,第二個專案的不足空間則是800-第一個專案實際寬度-200*4,依次類推

最終計算公式其實和 flex-grow 計算差不多

Flex專案彈性量 = (Flex容器不足空間/所有flex-shrink總和)*當前Flex專案的flex-shrink

只不過,所以上面例子每個專案可以計算出實際寬度為

第一個 Flex 專案: 200+((800-200x5)/5)*1 = 160px

第二個 Flex 專案: 200+((800-160-200x4)/4)*1 = 160px

第三個 Flex 專案: 200+((800-160-160-200x3)/3)*1 = 160px

第四個 Flex 專案: 200+((800-160-160-160-200x2)/2)*1 = 160px

第五個 Flex 專案: 200+((800-160-160-160-160-200x1)/1)*1 = 160px

如果 Flex 專案的min-content大於flex-basis,那麼最終的實際寬度將會取該專案的min-content,比如改一下例子,將第一個 Flex 專案改成長單詞

<div class="container">
  <div class="item">XiaoyueXiaoyue</div>
  <div class="item">June</div>
  <div class="item">Alice</div>
  <div class="item">Youhu</div>
  <div class="item">Liehu</div>
</div>

可以看出瀏覽器最終採用的是第一個 Flex 專案的min-content作為實際寬度,相應的後面 Flex 專案的寬度會等前一個 Flex 專案計算完畢後在進行計算

比如第二個 Flex 專案寬度= 200+((800-228.75-200x4)/4)*1 = 142.81px

flex-shrink 為小數

同樣的 flex-shrink 也會出現小數的情況,也分為 Flex 專案的 flex-shrink 之和小於等於 1 和大於 1 兩種情況,如果大於 1 和上面的計算方式一樣,所以我們只看小於 1 的情況,將我們的例子改為

.item {
  flex-basis: 200px;
  flex-shrink: 0.1;
}

效果為

此時我們會發現 Flex 專案溢位了容器,所以我們便可以得出一個結論:Flex 專案的 flex-shrink 之和小於 1,Flex 專案會溢位 Flex 容器

下面看一下它的計算公式

Flex專案彈性量=Flex容器不足空間*當前Flex專案的flex-shrink

Flex專案實際寬度=flex-basis + Flex專案彈性量

比如上面例子的每個 Flex 專案計算結果為

第一個 Flex 專案寬度 = 200+(800-200x5)x0.1=180px,但是由於它本身的min-content為 228.75,所以最終寬度為 228.75

第二個 Flex 專案寬度 =200-(800-228.75-200x4)x0.1=117.125

第三個 Flex 專案寬度...

Flex 的對齊方式

Flex 中關於對齊方式的屬性有很多,其主要分為兩種,一是主軸對齊方式:justify-*,二是交叉軸對齊方式:align-*

首先改一下我們的例子,將容器設定為寬高為 500x400 的容器(部分屬性省略)

<div class="container">
  <div class="item">A</div>
  <div class="item">B</div>
  <div class="item">C</div>
</div>
.container {
  display: flex;
  width: 500px;
  height: 400px;
}
.item {
  width: 100px;
  height: 40px;
}

主軸對齊屬性

這裡以橫向為主軸,縱向為交叉軸

justify-content

justify-content的值可以為:

  • flex-start 預設值,主軸起點對齊

  • flex-end 主軸終點對齊

  • left 預設情況下和 flex-start 一致
  • right 預設情況下和 flex-end 一致
  • center 主軸居中對齊

  • space-between 主軸兩端對齊,並且 Flex 專案間距相等

  • space-around 專案左右周圍空間相等

  • space-evenly 任何兩個專案之間的間距以及邊緣的空間相等

交叉軸對齊方式

align-content

align-content 屬性控制整個 Flex 專案在 Flex 容器中交叉軸的對齊方式

**注意設定 align-content 屬性時候必須將 flex-wrap 設定成 wrap 或者 wrap-reverse。**它可以取得值為

  • stretch 預設值,當我們 Flex 元素不設定高度的時候,預設是拉伸的

比如將 Flex 元素寬度去掉

.item {
  width: 100px;
}

  • flex-start 位於容器開頭,這個和 flex-direction:屬性有關,預設在頂部

  • flex-end 位於容器結尾

  • center 元素居中對齊

  • space-between 交叉軸上下對齊,並且 Flex 專案上下間距相等

此時我們改下例子中 Flex 專案的寬度使其換行,因為如果 Flex 專案只有一行,那麼 space-between 與 flex-start 表現一致

.item {
  width: 300px;
}

  • space-around 專案上下週圍空間相等

  • space-evenly 任何兩個專案之間的上下間距以及邊緣的空間相等

align-items

align-items 屬性定義 flex 子項在 flex 容器的當前行的交叉軸方向上的對齊方式。它與 align-content 有相似的地方,它的取值有

  • stretch 預設值,當我們 Flex 元素不設定高度的時候,預設是拉伸的

  • center 元素位於容器的中心,每個當前行在圖中已經框起來

  • flex-start 位於容器開頭

  • flex-end 位於容器結尾

  • baseline 位於容器的基線上

比如給 A 專案一個 padding-top

.item:nth-of-type(1) {
  padding-top: 50px;
}

沒設定 baseline 的表現

設定 baseline 之後

通過上面的例子我們可以發現,如果想要整個 Flex 專案垂直對齊,在只有一行的情況下,align-items 和 align-content 設定為 center 都可以做到,但是如果出現多行的情況下 align-items 就不再適用了

align-self

上面都是給 Flex 容器設定的屬性,但是如果想要控制單個 Flex 專案的對齊方式該怎麼辦呢?

其實 Flex 佈局中已經考慮到了這個問題,於是就有個 align-self 屬性來控制單個 Flex 專案在 Flex 容器側交叉軸的對齊方式。

align-self 和 align-items 屬性值幾乎是一致的,比如我們將整個 Flex 專案設定為 center,第二個 Flex 專案設定為 flex-start

.container {
  display: flex;
  width: 500px;
  height: 400px;
  align-items: center;
}
.item {
  width: 100px;
  height: 40px;
}
.item:nth-of-type(2) {
  align-self: flex-start;
}

注意,除了以上提到的屬性的屬性值,還可以設定為 CSS 的關鍵詞如 inherit 、initial 等

交叉軸與主軸簡寫

place-content

place-contentjustify-contentalign-content 的簡寫形式,可以取一個值和兩個值,如果設定一個值那麼 justify-contentalign-content 都為這個值,如果是兩個值,第一個值為 align-content,第二個則是 justify-content

到這裡關於Flex佈局基本已經介紹完了,肯定會有些細枝末節沒有考慮到,這可能就需要我們在平時工作和學習中去發現了

點個贊吧~