按我想法的話,css 網格布局(grid)和彈性布局(Flexbox)應該同時出現才對,這樣網頁布局方案就變得完整了。事實是,彈性布局先出現,因為使用彈性布局創建類網格(grid-type)系統比使用浮動更加便捷,于是我們便得到了許多基于 Flexbox 的網格系統。實際上,Flexbox 的優勢并不是用來創建網格系統,這也是為什么有時我們在用它創建網格系統時,感覺很費勁的原因。
我準備開一個小系列的文章,花點時間解密一下 Flexbox——就像過去我在講解 Grid 一樣。我們將看到 Flexbox 的設計目的,它真正做得好的地方,以及為什么我們不選擇它作為布局方法。本文中將會介紹當我們在使用 display: flex 的時候,到底發生了什么?
Flex 容器
為了使用彈性布局,我們需要先有一個元素充當 Flex 容器的角色。容器使用 display: flex 聲明:
display: flex 到底做了什么呢?在 Display Module Level 3 規范中,定義每 display 屬性值由兩部分組成:內部顯示模型(inner display model)和外部顯示模型(outer display model)。我們使用 display: flex 的時候,實際定義的是 display: block flex。Flex 容器的外部顯示類型是 block,在文檔流(normal flow)中表現為塊級元素(block level element),內部顯示類型是 flex,所以容器的直接子元素將參與彈性布局。
當然,我們還可以使用 display: inline-flex 定義 Flex 容器,與上面聲明類似,它實際定義的是 display: inline flex。Flex 容器表現的像個行內元素,其直接子元素將參與彈性布局。
理解了元素的外部顯示模型和內部顯示模型,對理解元素及其子元素在頁面中的表現行為非常有幫助。你可以將這種思路帶入到任意其他類型的盒子中:這個元素的表現特征為何?它的子元素呢?答案是跟外部顯示模型和內部顯示模型有關。
行與列
定義好 Flex 容器后,一些初始值就開始起作用了。在我們沒有設置任何其他屬性的情況下,所有的 Flex 項目 會排列為一行。之所以如此,是因為 flex-direction 的初始值是 row。
flex-direction 屬性設置的是主軸(main axis)的方向,取值除了 row 之外,還包括:
- column
- row-reverse
- column-reverse
這些排列在一行的 Flex 項目,始于內聯維度(inline dimension)的起始邊緣(start edge)、按照在源碼中出現的結構順序,依次排列。在規范中,這個“起始邊緣”被稱為 main-start:
main-start 位于內聯維度的起始端
如果使用的是 column,則 Flex 項目從塊維度(block dimension)起始邊緣開始排列,從而形成一列。
main-start 位于塊維度的起始端
如果使用的是 row-reverse,則 main-start 和 main-end 的位置就調換了。因此,Flex 項目會一個個按照與之前相反的順序排列。
main-start 位于內聯維度的終端
column-reverse 的作用于此類似。需要知道的是,這些值沒有“改變 Flex 項目的順序”,順序變了只是表征而已,改變的其實是 Flex 項目從哪開始布局,也就是說改變的是 main-start 的位置。因此,Flex 項目按照反序顯示,因為它們是沿著容器 的另一邊開始布局的。
還有一個比較重要的點,就是 Flex 項目的排列順序上的不同只是純視覺上的。我們只是要求 Flex 項目從結束邊緣(end edge)開始顯示,對于屏幕閱讀器(screen reader)或是當你按 Tab 鍵切換元素的時候,結果順序還是在源碼中出現的順序。
上面多此重復按下 Tab 鍵,可以觀察按鈕被 focus 的順序與在源碼中出現的順序一樣,與顯示順序無關。
Flexbox 中的兩根軸
我們已經講了彈性布局里一個非常重要的特性:主軸方向能從行切換到列。理解這種軸切換,能使我們更好地理解網格布局中對齊的工作原理。網格屬于二維布局,我們幾乎可以使用在彈性布局中同樣的方式設置 Grid 項目在兩個軸上的對齊效果。
我們已經解釋了主軸,也就是 flex-direction 屬性值定義的那個方向。交差軸(cross axis)是另一個維度。如果你設置了 flex-direction: row,主軸是沿著行的,而交差軸沿著列向下。如果設置的是 flex-direction: column,則主軸沿著列向下,而交差軸則沿著行。這就是我們要介紹的彈性布局的另一個重要特性,兩個軸的方向與屏幕的物理 維度沒有關系,我們沒有討論從左到右的行,或從上到下的列,是因為情況并非總是如此。
書寫模式
上面在講行和列的時候,提到了內聯和塊維度。本文使用英文書寫的,是水平書寫格式(譯注:原文是英文,不過現在中文書寫格式也與英文一樣)。也就是當你為 Flexbox 指定為 row 排列方式的話,會得到水平排列的 Flex 項目。這種情況下,main-start 在左邊——也就是英文句子開始的地方。
像在阿拉伯這樣的語言方向從右向左的國家,那么起始邊緣始于右邊。
當我只是創建了一個 Flex 容器的時候,Flexbox 的初始值表明 Flex 項目從右邊開始,向左排列顯示。內聯方向(inline direction)的起始邊緣就是我們使用的書寫模式下句子開始的地方。
垂直書寫模式(vertical writing mode)下的行是垂直的,因為在垂直語言環境下行就是垂直的,文本也是垂直顯示的。我們在 Flex 容器上設置 writing-mode: vertical-lr 就能看到效果。這時候,當你設置 flex-direction 為 row 的時候,就能看到在垂直方向上顯示的 Flex項目。
因此,行可以是水平顯示,main-start 在左邊或右邊,當然也可以是垂直的,main-start 在頂部。當你習慣于水平排列思維,看到垂直書寫模式下設置了 flex-direction: row 的 Flex容器中的項目 是垂直排列的,可能感覺很奇怪,不過人家確實是在行的方向上排列的。
要讓 Flex 項目在塊維度上布局的話,為 flex-direction 設置 column 或 column-reverse。在英語環境下,我們看見 Flex 項目從 Flex 容器頂部開始、依次往下布局排列的。
在垂直書寫模式下,塊維度是橫跨頁面的,類似于此種模式下塊元素的排列方式。如果設置了 vertical-lr,則塊元素是從左到右排列的。
但不論塊元素是在什么方向顯示排列的,如果 flex-direction 設置 column 的話,那么就是在塊維度上布局的。
理解了行和列在不同的書寫模式中,表現在不同的物理方向上,對理解 Grid 和 Flexbox 中的一些術語非常有用。我們沒有在 Grid 和 Flexbox 中提到“從左到右”或是“從上到下”是因為我們沒有做任何文檔書寫模式的假設。我們現在的 CSS 正在變得更加兼容書寫模式,如果你想了解更多表現行為于此類似的其他屬性和值的話,可以閱讀我寫的文章 Logical Properties and Values(https://www.smashingmagazine.com/2018/03/understanding-logical-properties-values/)。
好了,我們來總結一下:
- flex-direction: row
- 主軸 = 內聯維度
- main-start 位于在給定書寫模式下句子起始的地方
- 交叉軸 = 塊維度
- flex-direction: column
- 主軸 = 塊維度
- main-start 位于給定書寫模式下塊元起始布局的地方
- 交叉軸 = 內聯維度
默認對齊
使用 display: flex 后,還會有其他一些事情發生。在這個系列的之后的文章里,我會好好講解一下對齊。本文中,我們先來看下使用 display: flex 后,使用的一些默認值。
注意: 對齊屬性最開始起源于 Flexbox 規范。但正如 Flexbox 規范中解釋的那樣,BoxAlignment 規范最終將取代 Flexbox 規范中定義的這些屬性。
主軸對齊
justify-content 的初始值是 flex-start,就像我們在 CSS 中這樣聲明了:
.container { display: flex; justify-content: flex-start; }
這就是為什么 Flex 項目是從容器的起始邊緣開始排列的原因。而 row-reverse 則是調換了起始邊緣與結束邊緣,結束邊緣變為主軸開始的地方。
當你看見以 justify- 前綴開頭的屬性時,說明它是控制 Flexbox 主軸上對齊的。justify-content 操作主軸對齊,所有 Flex 項目在開始處對齊。
除了 flex-star,justify-content 還可使用的值包括:
- flex-end
- center
- space-around
- space-between
- space-evently(在 Box Alignment 中添加)
這些值用于分配 Flex 容器可用空間(available space)。這是項目移動和間隔的原因。如果使用了 justify-content: space-between,可用空間在項目之間平均分配,當然了,前提是有空間可供分配。如果 Flex 容器空間過窄(所有項目 布局完成后沒有額外的空間了),justify-content 就不起任何作用了。
我們可以設置 flex-direction 為 column 看下,此時 Flex 容器因為沒有設置 height ,所以不存在剩余空間,justify-content: space-between 也不會起作用。如果你設置一個足夠高的 height,待所有項目 布局好后,因為還有剩余空間,就會看到效果。
交叉軸對齊
Flex 項目還可以在只有一行的 Flex 容器上,執行交叉軸對齊。這種對齊控制的是盒子們在這跟線上的對齊方式。下例中,有一個盒子的內容比其他的要多,然后其他盒子像被通知了一樣伸展(stretch)到同樣的高度。這是 align-items 屬性初始值 stretch 在起作用:
當你看見以 align- 前綴開頭的屬性時,說明它是控制 Flexbox 交叉軸上對齊的。align-items 操作交叉軸對齊,所有 Flex 項目在彈性線(flex line)內對齊。
除了 stretch,align-items 還可以取的值包括:
- flex-start
- flex-end
- center
- baseline
如果不想要盒子伸展到最高那個的高度,可以設置 align-items: flex-start。讓項目在交叉軸的起始邊緣對齊。
Flex 項目的初始值
Flex 項目是有初始設置的,如下:
- flex-grow: 0
- flex-shrink: 1
- flex-basis: auto
就是說項目默認不會擴展(grow)來填充主軸上的可用空間。如果給 flex-grow 設置了一個正值,那么項目就會擴展剩余的空間。
項目的 flex-shrink 屬性處置值為正值 1,表示能收縮(shrink)。也就是說,當 Flex 容器比較窄的時候,在沒有任何溢出發生的情況下,項目會變得盡可能小。這是一個明智的行為,一般來說,我們希望盒子里的內容不要溢出。
為了默認能夠得到很好地布局,flex-basis 初始值使用了 auto。我會在以后的系列文章中解釋這個屬性的行為。你可以暫時把它認為是“大小剛剛好”(big enough to fit the content)。從頁面表現上看的話,就是內容較多的那個項目分配到的空間會比內容少的要多。
這就是使用 Flexbox 帶來的靈活性、設置了 flex-basis 為 auto,在沒有給項目設置額外大小限制的情況下愛,Flex 項目 有一個基礎尺寸(base size)max-content。這個寬度是指項目中的內容在完全沒有折行的情況下的長度。然后每個項目會 在占據的基礎尺寸的基礎上,按比例拿走一部分空間,這在 flexbox 規范中有詳情描述。
“注意: Flex shrink 因子分配負空間的時候,是在 Flex 項目的基礎尺寸的基礎之上增加的。最終分配的負空間多少,是根據因子值,按比例分配到每個項目頭上的。在大一點的項目 沒有顯著收縮之前,小的項目是不會收縮到零的。”
大一點的項目被抽取的空間相對(自身)來說是少的。你可以比較下面兩張圖,第一張圖里每個項目 的內容量差不多,所以看起來差不多是一樣寬的。在第二張圖里,第三項目的內容比較多,結果看到它分配到了更多的空間。
內容多的項目分配到了更多的空間
Flexbox 在我們沒有使用更多屬性的前提下,盡可能的提供給我們一個合理的布局結果。與一刀切平均分配每個項目 一樣的寬度、導致可能出現有內容多項目被擠壓的非常高的情況不同的是,彈性布局會給內容多的項目 分配更多的顯示空間。這種行為是彈性布局最佳的使用場景。 彈性布局最擅長于以一種靈活和內容感知的方式——沿著一個軸——放置一組項目。本文到此只是稍微接觸了一些皮毛,在本系列后面的文章中我會適當地講解這些算法。
總結
本文中,我們講解了彈性布局中使用到的一些初始值,解釋了當我們聲明了 display: flex 的時候,實際發生了什么。一路分析下來,發現東西還是很多的,文中涉及到幾個屬性牽涉到彈性布局的許多關鍵特性。
彈性布局如此靈活:默認情況下,它試圖對內容做出正確選擇——壓縮或拉伸 Flex 項目以獲得最佳的可讀性。彈性布局還支持書寫模式(writing mode):行和列的方向與所使用的書寫模式相關。通過選擇空間的分布方式,彈性布局允許將 Flex 項目作為一個組在主軸上對齊;我們還可以在一個伸縮線(flex line)中對齊項目,在交叉軸上以彼此聯系的方式移動 Flex 項目。重要的是,彈性布局能感知 Flex 項目的內容大小,會嘗試在沒有設置其他額外屬性的情況下,來合理的分配空間給各個 項目。在以后的文章中,我們將更深入地討論這些知識點,并做進一步分析什么時間以及為什么去選擇使用彈性布局。
版權
作者:zhangbao90s
鏈接:https://juejin.im/post/5d2da274f265da1bb003f242
著作權歸作者所有。