[譯] 當你建立 Flexbox 佈局時,都發生了什麼?

快速簡介: 在個人理想世界裏,CSS Grid 和 Flexbox 應看成爲一個總體一塊兒出現,才能組成完整的網頁佈局系統。然而,咱們先有了 Flexbox 屬性,由於它比浮動(floats)更適合用來作網格佈局,因此出現了不少以 Flexbox 爲基礎的網格佈局系統。事實上,不少人以爲 Flexbox 彈性佈局使人困擾或者難以理解,都是由於嘗試把它做爲網格佈局的方法。css

在接下來的一系列文章裏面,我會花一點時間來詳細講解 Flexbox 彈性佈局 —— 以以前我用來理解 grid 屬性的方式。咱們一塊兒看看 Flexbox 設計是爲了什麼,它擅長處理什麼,以及咱們爲何不選擇它做爲佈局的方法。在這篇文章中,咱們會詳細看一下,當你在樣式表添加 display: flex 的時候,究竟會發生什麼。html

一個 Flex 容器,拜託了!

爲了使用 Flexbox 佈局,你須要一個元素做爲 flex 容器,在 CSS 中,使用 display: flex前端

HTML:android

<div class="container">
  <div class="item">1</div>
  <div class="item">2</div>
  <div class="item">3</div>
</div>
複製代碼

CSS:ios

body {
  padding: 20px;
  font: 1em Helvetica Neue, Helvetica, Arial, sans-serif;
}

* {box-sizing: border-box;}

p {
  margin: 0 0 1em 0;
}

.container {  
  border: 5px solid rgb(111,41,97);
  border-radius: .5em;
  padding: 10px;
  display: flex;
  flex-direction: row-reverse;
}

.item {
  width: 100px;
  height: 100px;
  padding: 10px;
  background-color: rgba(111,41,97,.3);
  border: 2px solid rgba(111,41,97,.5);
}
複製代碼

請看 Rachel Andrew(@rachelandrew)在 CodePen 上的這個例子:Smashing Flexbox Series 1: display: flex;git

讓咱們花一點點時間來思考一下到底 display: flex 意味着什麼。在 Display Module Level 3 中,display 的每個值都由兩個部分構成:內部顯示模式和外部顯示模式。當咱們使用 display: flex 時,咱們其實定義的是 display: block flex。flex 容器的外部顯示模式是 block,它在文檔流中顯示爲正常的塊級元素。內部顯示模式是 flex,因此在容器內部的直接子元素按照彈性佈局來排列。github

可能你以前沒仔細想過,但其實已經知道了。flex 容器在頁面中和其餘塊級元素的表現同樣。若是你在 flex 容器後面緊跟一個段落,它們兩個都會表現爲正常的塊級元素。算法

咱們也能夠把容器的屬性設置爲 inline-flex,就和設置成 display:inline flex 同樣。好比說有一個行內級別的 flex 容器,容器裏還有一些參與 flex 佈局的子元素。這個 flex 容器內的子元素的表現就和塊級 flex 容器內的子元素表現同樣,不一樣之處就在於容器自己在總體佈局中的表現。後端

HTML:bash

<div class="container">
  <div class="item">1</div>
  <div class="item">2</div>
  <div class="item">3</div>
</div>

<em>這個 flex 容器是行內元素,因此另外一個行內元素會緊跟它後面顯示。</em>
複製代碼

CSS:

body {
  padding: 20px;
  font: 1em Helvetica Neue, Helvetica, Arial, sans-serif;
}

* {box-sizing: border-box;}

p {
  margin: 0 0 1em 0;
}

.container {  
  border: 5px solid rgb(111,41,97);
  border-radius: .5em;
  padding: 10px;
  display: inline-flex;
}

.item {
  width: 100px;
  height: 100px;
  padding: 10px;
  background-color: rgba(111,41,97,.3);
  border: 2px solid rgba(111,41,97,.5);
}
複製代碼

請看 Rachel Andrew(@rachelandrew)在 CodePen 上的這個例子:Smashing Flexbox Series 1: display: inline-flex;

元素的外部顯示模式決定了它做爲盒模型在頁面中怎樣顯示,同時與內部顯示模式一塊兒,決定了其子元素的行爲。這是一個頗有用的思想。你能夠把這種思想應用於任何 CSS 定義的盒模型。這個元素將會如何表現?它的子元素又會怎樣表現?這些問題的答案就與它們的內部顯示模式和外部顯示模式有關。

行或者列?

一旦咱們定義了 flex 容器以後,一些默認值就開始發揮做用了。在咱們沒有添加任何其餘屬性的狀況下,flex 子項目(flex item)會按照行來排列。這是由於 flex-direction 屬性的默認值就是 row。若是你不對它進行設置,它就會按照行的方向來顯示。flex-direction 屬性是用來設置主軸(main axis)的排列方向,這個屬性還有其餘的值:

  • column
  • row-reverse
  • column-reverse

當子項目排成一行的時候,子項目會按照在文檔中的順序,從行內維度的起始邊緣依次排列。在規範中,這個邊緣就被叫作 main-start

main-start 是一行的開始

main-start 是行內維度的起始位置(Large preview)。

若是咱們使用 column,子項目從塊級維度的起始邊緣開始排列,所以構成一列。

子項目按照列來排列,main-start 位於頂部

main-start 是塊級維度的起始位置(Large preview)。

當咱們使用 row-reverse 時,main-startmain-end 的位置互換了,所以,子項目也會相應的按照相反的順序來排列。

子項目從行的末尾開始排列

main-start 在內聯維度的末尾(Large preview)。

column-reverse 也具備同樣的效果。另外還有一點很重要,那就是這些值並不會「改變子項目的順序」,儘管它們看起來是這樣的效果,它們改變的是這些子項目開始排列的位置:經過改變 main-start 來達到目的。因此咱們的子項目會按照相反的方向來排列,這僅僅是由於它是從容器的結束位置開始排列的。

另外還有一件很重要的事情要記住,當排列順序發生改變時,這僅僅是視覺上的,由於咱們要求子項目從結束位置開始排列。它們在文檔中仍然是原來的順序,當你使用屏幕閱讀器的時候仍然是按照源文檔的順序進行索引。若是你真的想要改變子元素的順序,不該該使用 row-reverse,而是直接在文檔源中去改變子項目的順序。

Flexbox 的兩條軸線

咱們已經講解了 flexbox 的一個重要特性:可以將主軸(main axis)的方向從行切換爲列。這種軸的方向切換,就是爲何我經常認爲網格佈局中的對齊更容易理解的緣由。由於在網格佈局中,在兩個方向上你均可以採用幾乎相同的方式來實現對齊。而對於彈性佈局來講會更麻煩點,由於在主軸(main axis)和交叉軸(cross axis)上,子項目的表現是不太相同的。

咱們已經瞭解了主軸(main axis),即你用 flex-direction 屬性的值來定義的那根軸線。交叉軸(cross axis)則在另外一個方向上。若是你設置 flex-direction: row,那麼你的主軸(main axis)是沿着行的方向,你的交叉軸(cross axis)是沿着列的方向。若是設置 flex-direction: column,主軸(main axis)是沿着列的方向,交叉軸(cross axis)是沿着行的方向。在這裏咱們就須要討論 flexbox 的另一個重要特色,那就是它與屏幕的物理方向無關。咱們不討論從左到右方向的行,或從上到下方向的列,由於狀況並不是老是如此。

書寫模式

當我在上文中描述行和列的時候,我提到了塊級和行內的維度 dimensions。這篇文章是用英文寫的,它是水平的書寫模式。這就意味着當你要 Flexbox 顯示一行時,子項目會水平的展現。在這個例子中,main-start 位於左邊 —— 也就是英文書寫模式中中句子開始的位置。

若是我使用的是從右到左書寫的語言,好比阿拉伯語的話,起始位置就會位於右邊:

HTML:

<div class="container">
  <div class="item">1</div>
  <div class="item">2</div>
  <div class="item">3</div>
</div>
複製代碼

CSS:

body {
  padding: 20px;
  font: 1em Helvetica Neue, Helvetica, Arial, sans-serif;
}

* {box-sizing: border-box;}

p {
  margin: 0 0 1em 0;
}

.container {  
  border: 5px solid rgb(111,41,97);
  border-radius: .5em;
  padding: 10px;
  display: flex;
  flex-direction: row;
  direction: rtl;
}

.item {
  width: 100px;
  height: 100px;
  padding: 10px;
  background-color: rgba(111,41,97,.3);
  border: 2px solid rgba(111,41,97,.5);
}
複製代碼

請看 Rachel Andrew(@rachelandrew)在 CodePen 上的這個例子:Smashing Flexbox Series 1: row with rtl text

flexbox 的初始值意味着,若是我所作的只是建立一個 flex 容器,個人子項目將會從右側開始顯示,而且向左排列。內聯方向的起始位置是你正在使用的書寫模式中句子開始的位置

若是你使用垂直書寫模式而且使用的默認排列方向(這裏指 flex-direction: row),此時的行就會是垂直方向的,由於這就是垂直書寫方式語言排列行的方式。你能夠嘗試爲 flex 容器設置 writing-mode 屬性,把值設置爲 vertical-lr。如今,你再把 flex-direction 設置爲 row,子項目就會排成垂直的一列了。

HTML:

<div class="container">
  <div class="item">1</div>
  <div class="item">2</div>
  <div class="item">3</div>
</div>
複製代碼

CSS:

body {
  padding: 20px;
  font: 1em Helvetica Neue, Helvetica, Arial, sans-serif;
}

* {box-sizing: border-box;}

p {
  margin: 0 0 1em 0;
}

.container {  
  border: 5px solid rgb(111,41,97);
  border-radius: .5em;
  padding: 10px;
  display: flex;
  flex-direction: row;
  writing-mode: vertical-lr;
}

.item {
  width: 100px;
  height: 100px;
  padding: 10px;
  background-color: rgba(111,41,97,.3);
  border: 2px solid rgba(111,41,97,.5);
}
複製代碼

請看 Rachel Andrew(@rachelandrew)在 CodePen 上的這個例子:Smashing Flexbox Series 1: row with a vertical writing mode

因此,一行能夠水平的排列,main-start 位於左側或者右側,也能夠垂直排列,main-start 位於頂部。即便咱們的思惟習慣了橫向排列的文本,很難想象一行垂直排列的文本,但它的 flex-direction 屬性的值仍然是 row

爲了讓子項目按照塊級維度進行排列,咱們能夠把 flex-direction 的值設置成 column 或者 column-reverse。在英語(或者阿拉伯語)這樣的水平書寫模式裏,子項目會從容器頂部開始按照垂直方向排列。

在垂直書寫模式中,塊級方向橫跨整個頁面,這也是這種書寫模式下塊級元素的排列方向。若是你將一列設置爲爲 vertical-lr,那麼這些塊級元素會從左到右進行排列(內部的文本方向仍爲垂直排列)。

HTML:

<div class="container">
  <div class="item">1</div>
  <div class="item">2</div>
  <div class="item">3</div>
</div>
複製代碼

CSS:

body {
  padding: 20px;
  font: 1em Helvetica Neue, Helvetica, Arial, sans-serif;
}

* {box-sizing: border-box;}

p {
  margin: 0 0 1em 0;
}

.container {  
  border: 5px solid rgb(111,41,97);
  border-radius: .5em;
  padding: 10px;
  display: flex;
  flex-direction: column;
  writing-mode: vertical-lr;
}

.item {
  width: 100px;
  height: 100px;
  padding: 10px;
  background-color: rgba(111,41,97,.3);
  border: 2px solid rgba(111,41,97,.5);
}
複製代碼

請看 Rachel Andrew(@rachelandrew)在 CodePen 上的這個例子:Smashing Flexbox Series 1: column in vertical-lr writing mode

可是,不管塊級元素怎麼顯示,只要你使用的是 column 方向,那麼元素始終處在塊級維度之中。

瞭解一行或者一列可以在不一樣的物理方向上運行,有助於咱們理解網格佈局和彈性佈局中的一些術語。在網格佈局和彈性佈局中,並不會使用『左和右』、『上和下』這樣的方向,由於咱們並不會指定文檔的書寫模式。如今全部的 CSS 都變的更注重書寫模式了。若是你對其餘已經支持這種方向差別的 CSS 屬性和值有興趣的話,能夠讀一下個人這篇文章 Logical Properties and Values

總結一下,記住:

  • flex-direction: row

    • 主軸 = 行內維度
    • main-start 位於當前書寫模式下句子開頭的位置
    • 交叉軸 = 塊級維度
  • flex-direction: column

    • 主軸 = 塊級維度
    • main-start 位於當前書寫模式下塊級元素的開頭位置
    • 交叉軸 = 行內維度

默認對齊方式

當咱們設置 display: flex 時,還會發生一些事情,默認的對齊方式會發揮做用。在該系列的其餘文章中,咱們會好好地瞭解一下對齊方式。可是,咱們如今在探索 display: flex 的時候,也應該看一下這些發揮做用的默認值。

注意值得注意的是,儘管這些對齊屬性始於 Flexbox 規範,但 Box Alignment(盒模型對齊)會最終覆蓋 Flexbox 規範的相關內容,如 flexbox 規範中所述。

主軸對齊方式

justify-content 屬性的默認值是 flex-start,就像咱們的 CSS 寫的那樣:

.container {
    display: flex;
    justify-content: flex-start;
}
複製代碼

這就是咱們的 flex 子項目(flex item)從 flex 容器的起始邊緣開始排列的緣由。一樣也是當咱們設置 row-reverse 時,它們變爲從結束邊緣開始排列的緣由,由於那個邊緣變成了主軸(main axis)的起點。

當你看見對齊屬性以 justify- 開頭時,這個屬性就是做用於主軸(main axis)的。也就是說 justify-content 屬性規定主軸(main axis)的對齊方式,並將子項目從起始邊緣對齊。

justify-content 還有其餘的值:

  • flex-end
  • center
  • space-around
  • space-between
  • space-evenly(在塊級對齊方式中添加)

這些值用來處理 flex 容器中剩餘空間的分佈。這就是子項目間會發生移動或者說相互分隔的緣由了。若是你添加屬性 justify-content: space-between,剩餘空間被平均分配給子項目。固然,這隻有在容器有剩餘空間的狀況下才會發生。若是你的 flex 容器被所有充滿了(子項目排列完後,沒有任何剩餘空間),那麼設置 justify-content 屬性不會產生任何效果。

你能夠把 flex-direction 設置成 column。由於 flex 容器在沒有高度的狀況下不會有剩餘空間,因此設置 justify-content: space-between 不會發生變化。若是你把容器設置成比展現所須要的高度更高的話,那麼這個屬性就會發揮做用了:

HTML:

<div class="container">
  <div class="item">1</div>
  <div class="item">2</div>
  <div class="item">3</div>
</div>
複製代碼

CSS:

body {
  padding: 20px;
  font: 1em Helvetica Neue, Helvetica, Arial, sans-serif;
}

* {box-sizing: border-box;}

p {
  margin: 0 0 1em 0;
}

.container {  
  border: 5px solid rgb(111,41,97);
  border-radius: .5em;
  padding: 10px;
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  height: 500px;
}

.item {
  width: 100px;
  height: 100px;
  padding: 10px;
  background-color: rgba(111,41,97,.3);
  border: 2px solid rgba(111,41,97,.5);
}
複製代碼

請看 Rachel Andrew(@rachelandrew)在 CodePen 上的這個例子:Smashing Flexbox Series 1: column with a height

交叉軸的對齊方式

子項目在單一交叉軸的 flex 容器中也會沿着這根交叉軸對齊。這裏執行的對齊是子項目沿着交叉軸相互對齊。在接下來的這個例子中,有一個子項目比其餘項佔據更高的空間,而後其餘的子項目會按照某種規範來拉伸到與它相同的高度,這個規範就是 align-items 屬性,由於它的初始值就是 stretch

HTML:

<div class="container">
  <div class="item">One</div>
  <div class="item">Two</div>
  <div class="item">
    <ul>
    <li>Three: a</li>
    <li>Three: b</li>
    <li>Three: c</li>
    </ul>
    </div>
</div>
複製代碼

CSS:

body {
  padding: 20px;
  font: 1em Helvetica Neue, Helvetica, Arial, sans-serif;
}

* {box-sizing: border-box;}

p {
  margin: 0 0 1em 0;
}

.container {  
  border: 5px solid rgb(111,41,97);
  border-radius: .5em;
  padding: 10px;
  display: flex;
}

.item {
  padding: 10px;
  background-color: rgba(111,41,97,.3);
  border: 2px solid rgba(111,41,97,.5);
}

.item ul {
  margin: 0;
  padding: 0;
  list-style: none;
}
複製代碼

請看 Rachel Andrew(@rachelandrew)在 CodePen 上的這個例子:Smashing Guide to Layout: clearfix

當你在 flex 佈局中看到有一個屬性是以 align- 開頭的,那個就是交叉軸的對齊方式,align-items 屬性規定子項目在沿着交叉軸方向上的對齊方式,這個屬性的其餘的值包括:

  • flex-start
  • flex-end
  • center
  • baseline

若是你不想其餘的子項目跟拉伸到跟最高的那一項同樣高的話,設置 align-items: flex-start,它會把子項目都沿着交叉軸的起始位置對齊。

HTML:

<div class="container">
  <div class="item">One</div>
  <div class="item">Two</div>
  <div class="item">
    <ul>
    <li>Three: a</li>
    <li>Three: b</li>
    <li>Three: c</li>
    </ul>
    </div>
</div>
複製代碼

CSS:

body {
  padding: 20px;
  font: 1em Helvetica Neue, Helvetica, Arial, sans-serif;
}

* {box-sizing: border-box;}

p {
  margin: 0 0 1em 0;
}

.container {  
  border: 5px solid rgb(111,41,97);
  border-radius: .5em;
  padding: 10px;
  display: flex;
  align-items: flex-start;
}

.item {
  padding: 10px;
  background-color: rgba(111,41,97,.3);
  border: 2px solid rgba(111,41,97,.5);
}

.item ul {
  margin: 0;
  padding: 0;
  list-style: none;
}
複製代碼

請看 Rachel Andrew(@rachelandrew)在 CodePen 上的這個例子:Smashing Flexbox Series 1: align-items: flex-start

flex 子項目的初始值

終於說到這裏了,flex 子項目(flex item)也是有初始值的,它們包括:

  • flex-grow: 0
  • flex-shrink: 1
  • flex-basis: auto

這意味咱們的子項目(flex item)在默認狀況下不會自動充滿主軸上的剩餘空間。若是 flex-grow 被設置成一個正數,纔會致使子項目拉伸並佔據剩餘空間。

這些子項目(flex item)一樣能夠收縮。默認狀況下,flex-shrink 的值被設置成了 1。這就意味着,若是咱們的 flex 容器很是小,那麼其中的子元素在溢出容器以前就會自動的縮小以適應容器大小。這是一個很是靈活的屬性。總的來講就是,子項目在容器沒有足夠空間去排列的狀況下依然能保持在容器以內,而且不會溢出。

爲了在默認狀況下得到最好的展現效果,flex-basis 屬性的默認值被設置成 auto,咱們會在這個系列的其餘文章中好好了解這表明什麼。如今,你只須要將 auto 理解爲『大到足夠適應容器』就好了。在這種狀況下,當 flex 容器中有一些子項目,其中的一個子項目相較於其餘包含更多的內容,那麼它會被分配更多的空間。

HTML:

<div class="container">
  <div class="item">Two words</div>
  <div class="item">Now three words</div>
  <div class="item">
    This flex item has a lot of content and so it is going to need more space in the flex container.
    </div>
</div>
複製代碼

CSS:

body {
  padding: 20px;
  font: 1em Helvetica Neue, Helvetica, Arial, sans-serif;
}

* {box-sizing: border-box;}

p {
  margin: 0 0 1em 0;
}

.container {  
  border: 5px solid rgb(111,41,97);
  border-radius: .5em;
  padding: 10px;
  display: flex;
  width: 400px;
}

.item {
  padding: 10px;
  background-color: rgba(111,41,97,.3);
  border: 2px solid rgba(111,41,97,.5);
}
複製代碼

請看 Rachel Andrew(@rachelandrew)在 CodePen 上的這個例子:Smashing Flexbox Series 1: initial values of flex items

上面這個例子就是彈性佈局靈活性的體現。在 flex-basis 屬性的默認值是 auto,而且子項目(flex item)沒有設置尺寸的狀況下,它就會有一個 max-content 的基礎尺寸。這就是它根據內容自動延伸以後沒有通過任何其餘包裝的尺寸。而後,子項目按照比例來佔據剩餘空間,詳見如下 flexbox 規範中的說明。

『注意:分配收縮空間時,是根據基本尺寸乘以彈性收縮係數(flex shrink factor)。收縮空間的大小與子項目設置的 flex-shrink 的大小成比例。好比說有一個較小的子項目,在其餘較大的子項目明顯收縮以前,是不會被收縮到沒有空間的。』

也就是說較大的子項目收縮的空間更多,那麼如今咱們獲得了最終的佈局結果。你能夠比較一下下面兩個截圖,都是使用上面的例子,不一樣的是在第一個截圖中,第三個盒子內容較少佔據的空間較小,所以相對的每一列的佔據的空間更均勻一些。

這是較大的子項目佔據更多空間的例子

其餘項爲了給較大的一項提供空間而自動收縮(Large preview)。

彈性佈局會試圖幫助咱們得到一個合理的最終顯示結果,而不須要寫 CSS 的人來定義。它不會平均的減小每行的寬度,從而造成一個每行只有幾個單詞的很高的子項目,而是會給該子項目分配更多的空間用以展現其內容。這種表現正是如何正確彈性佈局的關鍵。它最適用於用於沿着一條軸線排列的元素,以一種靈活和感知內容的方式。這裏我簡單介紹了一點細節,但在接下來的系列文章中咱們會更加深刻的瞭解這些算法。

總結

在這篇文章中,我用彈性佈局的屬性的一些默認值來介紹當你設置 display: flex 的時候,究竟發生了什麼。使人驚訝的是,當你逐步分解以後,發現它原來有這麼多內容,而且這些內容就包含了彈性佈局的核心特色。

彈性佈局是很是靈活的:它會根據你的內容自動地作出不錯的選擇 —— 經過收縮和拉伸達到最好的展現效果。彈性佈局還能感知書寫模式:佈局中行和列的方向跟書寫模式有關。彈性佈局經過分配空間,容許子項目在主軸(main axis)上以總體的的方式來對齊。它還容許子項目按照交叉軸來對齊,使得交叉軸上的項目相互關聯。更重要的是,彈性佈局知道你的內容有多大,而且儘可能採用更好的方式來展現你的內容。在接下來的文章中,咱們會更加深刻的探索,思考咱們何時以及爲何要用彈性佈局。

若是發現譯文存在錯誤或其餘須要改進的地方,歡迎到 掘金翻譯計劃 對譯文進行修改並 PR,也可得到相應獎勵積分。文章開頭的 本文永久連接 即爲本文在 GitHub 上的 MarkDown 連接。


掘金翻譯計劃 是一個翻譯優質互聯網技術文章的社區,文章來源爲 掘金 上的英文分享文章。內容覆蓋 AndroidiOS前端後端區塊鏈產品設計人工智能等領域,想要查看更多優質譯文請持續關注 掘金翻譯計劃官方微博知乎專欄

相關文章
相關標籤/搜索