Web layout 是Web UI中的基礎架構, 重要性不言而喻. 傳統的盒模型, 藉助display, position, float 屬性應對普通佈局遊刃有餘, 但針對複雜的或自適應佈局, 常常捉襟見肘. 比如垂直居中, 就是一個老大難的問題, 藉助flex彈性盒模型, 兩行程式碼就可以優雅的實現之. (該方法曾在 16種方法實現水平居中垂直居中 一文中提到). 當然, 本次我們不會只討論垂直居中的問題, 我將努力盡可能的還原flex的應用場景.
原文: 彈性盒模型Flex指南
本文將介紹flex子專案壓縮比計算, 多層flex巢狀的常見問題. 通讀本文, 你還將瞭解如下內容:
Flex
Flex即彈性盒模型, 該佈局方案由W3C於2009年提出. 此後, Flex方案便歷經v2009, v2011, v2012, v2014, v2015, v2016等版本, 最近方案是2016年5月26日起草的 CSS Flexible Box Layout Module Level 1.
相容性
首先, 我們來回顧下如今PC端的相容性(以下為完全相容版本).
IE | Edge | Firefox | Chrome | Safari | Opera |
---|---|---|---|---|---|
– | 12+ | 28+ | 21+ | 6.1+ | 12.1+ |
以上, IE10+僅支援2012版W3C的flex語法, 且存在較多已知的bug, 此時使用flex佈局需謹慎.
Chrome瀏覽器v21~v28版本需要新增 “-webkit-” 字首.
Safari瀏覽器v6.1~v8版本需要新增 “-webkit-” 字首.
Opera瀏覽器v15~v16版本需要新增 “-webkit-” 字首.
因此, 看到一些sass編譯後的css檔案中帶有 “-webkit-” 字首無需驚慌.
平時開發時最為擔心的便是移動端相容性, 請看:
IOS Safari | Opera mini | Android | Android Chrome | UC | 微信 |
---|---|---|---|---|---|
7.1+ | √ | 4.4+ | 55 | – | 當前支援 |
微信當前版本已支援flex.
UC不對外提供webview核心, 除去一些H5app的應用, 各種分享頁基本(常在微信下開啟)基本不需要擔心對其相容性, 實在需要實現, UC還是支援老版本的彈性盒子的, 可以優雅降級. 可見, Android4.4以上基本可以安心使用flex.
Autoprefixer
強記各種瀏覽器的字首是沒有必要的, 因為autoprefixer該做的, 都幫我們做了. 因此建議嘗試下以下三個外掛之一.
優勢
Flex佈局使得子專案能夠”彈性”的改變其高寬, 自由填充容器剩餘空間, 以適應容器變大, 或者壓縮子專案自身, 以適應容器變小; 同時還可以方便的調節子專案方向和順序. flex常用於高寬需要自適應, 或子專案大小成比例, 或水平垂直對齊等場景.
概念鋪墊
Flex彈性盒模型裡, 有容器和專案之分. 設定display:flex
的為容器, 容器內的元素稱作它的子專案, 容器有容器的一套屬性, 子專案有子專案的另一套屬性. (可以這麼理解: father作為彈性盒子, 制定行為規範, son享受盒子的便利, 按照規範劃分各自的”轄區”).
以下圖片摘自大漠的一個完整的Flexbox指南文中.
father制定的規範, 基於兩個方向 — 水平和垂直.
- 水平方向的稱之為主軸(main axis), 垂直方向的稱之為交叉軸(cross axis).
- 主軸起始位置, 叫做
main start
, 末尾位置叫做main end
; - 交叉軸起始位置, 叫做
cross start
, 末尾位置叫做cross end
. - 子專案在主軸上所佔的寬(高)度, 叫做
main size
, 在交叉軸上所佔的高(寬)度, 叫做cross size
.
屬性
display: flex | inline-flex;(元素將升級為彈性盒子). 前者容器升級為塊級盒子, 後者容器將升級為行內盒子. 元素採用flex佈局以後, 子元素的float, clear, vertical-align屬性都將失效.
容器屬性
容器具有以下6個屬性.
- flex-direction 指定主軸的方向.
flex-direction的值 | 描述 |
---|---|
row(預設) | 指定主軸水平, 子專案從左至右排列➜ |
row-reverse | 指定主軸水平, 子專案從右至左排列⬅︎ |
column | 指定主軸垂直, 子專案從上至下排列⬇︎ |
column-reverse | 指定主軸垂直, 子專案從下至上排列⬆︎ |
- flex-wrap 指定如何換行.
flex-wrap的值 | 描述 |
---|---|
nowrap(預設) | 預設不換行 |
wrap | 正常換行 |
wrap-reverse | 換行, 且前面的行在底部 |
- flex-flow 它是flex-direction 和 flex-wrap的簡寫形式, 預設值為
row nowrap
. - justify-content 指定主軸上子專案的對齊方式.(通常為水平方向對齊方式)
justify-content的值 | 描述(子專案–主軸方向) |
---|---|
flex-start(預設) | 子專案起始位置與main start 位置對齊 |
flex-end | 子專案末尾位置與main end 位置對齊 |
center | 在主軸方向居中於容器 |
space-between | 與交叉軸兩端對齊, 子專案之間的間隔全部相等 |
space-around | 子專案兩側的距離相等, 它們之間的距離兩倍於它們與主軸起始或末尾位置的距離. |
- align-items 指定交叉軸上子專案的對齊方式.(通常為垂直方向對齊方式)
align-items的值 | 描述(子專案—交叉軸方向) |
---|---|
flex-start | 子專案起始位置與cross start 位置對齊 |
flex-end | 子專案末尾位置與cross end 位置對齊 |
center | 在交叉軸方向居中於容器 |
baseline | 第一行文字的基線對齊 |
stretch(預設) | 高度未定(或auto)時, 將佔滿容器的高度 |
- align-content 指定多根主軸的對齊方式. 若只有一根主軸, 則無效.
align-content的值 | 描述(子專案) |
---|---|
flex-start | 頂部與cross start 位置對齊 |
flex-end | 底部與cross end 位置對齊 |
center | 在交叉軸方向居中於容器 |
space-between | 與交叉軸兩端對齊, 間隔全部相等 |
space-around | 子專案兩側的距離相等, 它們之間的距離兩倍於它們與主軸起始或末尾位置的距離. |
stretch(預設) | 多根主軸上的子專案充滿交叉軸 |
子專案屬性
子專案具有以下6個屬性.
-
flex-grow 指定子專案的放大比例, 預設為0(即不放大). 該屬性可取值為任何正整數. 假設各個子專案的放大比例之和為n, 那麼容器內剩餘的空間將分配n份, 每個子專案各自分到x/n份. (x為該子專案的放大比例)
-
flex-shrink 指定子專案的縮小比例, 預設為
1
. 設定為0時, 空間不足該子專案將不縮小. 我們知道,容器的縮小總寬度=子專案所需要的總寬度-容器實際寬度
, 假設容器需要縮小的寬度為W, 某子專案的預設寬度為L, 其縮小比例為p, 那麼該子專案實際的寬度為L-p*W
.-
上面輕描淡寫的給出了子專案的縮小比例, 可能會給你一種錯覺— “縮小比例很容易計算”, 實際上, 我們在計算元素需要縮小比例時, 總是要考慮到元素自身預設的大小.
-
假設上述子專案其flex-shrink值為x1, 另一個子專案的預設寬度為R, flex-shrink值為x2, 考慮到元素自身大小. 最終第一個子專案的縮小比例是加權了自身預設大小後的結果, 即
rate = L*x1/(L*x1 + R*x2)
. -
為什麼計算會如此複雜, 如此不直觀??? 這是因為, 子專案的大小各不一致, 假如一個子專案是另一個子專案主軸寬度的9倍, 前者的flex-shrink值為1, 後者為9, 而容器實際上只有他們預設總寬度的一半. 這意味著, 這兩個子專案共計要壓縮為預設的一半. 如果僅僅按照flex-shrink值來決定比例, 那麼第二個子專案需要壓縮其預設的9/10, 而我們知道, 它預設是如此的小, 即使全部壓縮了, 也無濟於事; 而第一個元素僅需要壓縮其預設的1/10, 簡直就是九牛一毛, 根本達不到預設總寬度壓縮一半的效果. 很明顯, 這種壓縮比例的分配方式是不合理的. 因此最終的壓縮比例加入了預設寬度值(即flex-basis值), 表示式的分子為
flex-shrink * flex-basis
, 分母為各子專案flex-shrink * flex-basis
之和.
-
-
flex-basis 指定子專案分配的預設空間, 預設為
auto
. 即該子專案的原本大小. -
flex 是 flex-grow, flex-shrink, flex-basis 3個屬性的縮寫. 預設為
0 1 auto
. 該屬性取值為auto時等同於設定為1 1 auto
, 取值為none時等同於設定為0 0 auto
. -
align-self 指定單個子專案獨立的對齊方式. 預設為
auto
, 表示繼承父元素的align-items屬性, 如無父元素, 則等同於stretch
. 該屬性共有6種值, 其他值與上述align-items屬性保持一致. -
order 指定子專案的順序, 數值越小, 順序越靠前, 預設為
0
.
flex屬性的優先順序
我們可以給input設定flex:1
, 使其充滿一行, 並且隨著父元素大小變化而變化. 也可以給div設定flex:1
使其充滿剩餘高度.
使用flex佈局這些都不是難事, 需要注意的是, 這其中有坑. 為了避免踩坑, 我們先來看下flex屬性的優先順序:
width|height > 自適應文字內容的寬度或高度 > flex:數值
這意味著, 首先是元素寬高的值優先, 其次是內容的寬高, 再次是flex數值. 現在我們來看看坑是什麼.
- 給input元素設定
flex:1
時需要注意, 通常input擁有一個預設寬度(用於展示預設數量的字元), 在chrome v55下, 這個寬度預設為126px(同時還包含2px的border). 因此想要實現input寬度自適應, 可以設定其width為0. - 給div元素設定
flex:1
時, 因div的高度會受子級元素影響, 為了使得該div佔滿其父元素剩餘的高度, 且不超出, 建議將該div的height
屬性設定為0.
場景回顧
-
想要實現垂直居中的效果, 只需要設定父元素為
display:flex;justify-content:center
即可. (當然, 父元素樣式採用:display:table;
, 子元素樣式採用:display:table-cell;vertical-align:middle
也是可以實現的), 如下圖. -
想要實現左右兩個元素等高(父元素高度由子元素撐開), 並且各佔一半的寬度. 如上圖.
- 早期的實現方案, 需要藉助負margin. 父元素樣式設定為
overflow:hidden
, 子元素樣式設定為margin-bottom:-10000px;padding-bottom:10000px;
, 這樣, 每個子元素便能借助padding撐開, 同時, 藉助負margin和overflow合理裁剪. - 第二種方案就是藉助IE8都支援的
display:table
屬性, 父元素樣式設定為display:table
, 子元素設定為display:table-cell
. 利用表格的行高一致性, 輕鬆實現行高一致. - 最終, 我們發現, 還是flex彈性盒模型來得方便快捷, 它只需要父級元素樣式設定為
display:flex
.
- 早期的實現方案, 需要藉助負margin. 父元素樣式設定為
有關flex的舊語法, 請戳這篇回顧 Flex佈局新舊混合寫法詳解(相容微信) .
有關移動端的最佳實踐, 請戳這篇圍觀 移動端全相容的flexbox速成班 .
當然, 這裡還有一個 Flexbugs 列表, github上已有近6k的star, 感興趣可以前去看看.
本問就討論這麼多內容,大家有什麼問題或好的想法歡迎在下方參與留言和評論.
本文作者: louis
本文連結: louiszhai.github.io/2017/01/13/…
參考文章