彈性盒模型Flex指南

路易斯發表於2019-03-01

Web layout 是Web UI中的基礎架構, 重要性不言而喻. 傳統的盒模型, 藉助display, position, float 屬性應對普通佈局遊刃有餘, 但針對複雜的或自適應佈局, 常常捉襟見肘. 比如垂直居中, 就是一個老大難的問題, 藉助flex彈性盒模型, 兩行程式碼就可以優雅的實現之. (該方法曾在 16種方法實現水平居中垂直居中 一文中提到). 當然, 本次我們不會只討論垂直居中的問題, 我將努力盡可能的還原flex的應用場景.

原文: 彈性盒模型Flex指南

本文將介紹flex子專案壓縮比計算, 多層flex巢狀的常見問題. 通讀本文, 你還將瞭解如下內容:

  1. Flex
  2. 相容性
  3. Autoprefixer
  4. 優勢
  5. 概念鋪墊
  6. 屬性
    1. 容器屬性
    2. 子專案屬性
  7. flex屬性的優先順序
  8. 場景回顧

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指南文中.

彈性盒模型Flex指南
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).

      彈性盒模型Flex指南

    • 為什麼計算會如此複雜, 如此不直觀??? 這是因為, 子專案的大小各不一致, 假如一個子專案是另一個子專案主軸寬度的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數值. 現在我們來看看坑是什麼.

  1. 給input元素設定flex:1時需要注意, 通常input擁有一個預設寬度(用於展示預設數量的字元), 在chrome v55下, 這個寬度預設為126px(同時還包含2px的border). 因此想要實現input寬度自適應, 可以設定其width為0.
  2. 給div元素設定flex:1時, 因div的高度會受子級元素影響, 為了使得該div佔滿其父元素剩餘的高度, 且不超出, 建議將該div的height屬性設定為0.

場景回顧

  1. 想要實現垂直居中的效果, 只需要設定父元素為display:flex;justify-content:center 即可. (當然, 父元素樣式採用:display:table;, 子元素樣式採用:display:table-cell;vertical-align:middle 也是可以實現的), 如下圖.

    彈性盒模型Flex指南

  2. 想要實現左右兩個元素等高(父元素高度由子元素撐開), 並且各佔一半的寬度. 如上圖.

    • 早期的實現方案, 需要藉助負margin. 父元素樣式設定為overflow:hidden, 子元素樣式設定為margin-bottom:-10000px;padding-bottom:10000px;, 這樣, 每個子元素便能借助padding撐開, 同時, 藉助負margin和overflow合理裁剪.
    • 第二種方案就是藉助IE8都支援的display:table屬性, 父元素樣式設定為display:table , 子元素設定為display:table-cell. 利用表格的行高一致性, 輕鬆實現行高一致.
    • 最終, 我們發現, 還是flex彈性盒模型來得方便快捷, 它只需要父級元素樣式設定為display:flex.

有關flex的舊語法, 請戳這篇回顧 Flex佈局新舊混合寫法詳解(相容微信) .

有關移動端的最佳實踐, 請戳這篇圍觀 移動端全相容的flexbox速成班 .

當然, 這裡還有一個 Flexbugs 列表, github上已有近6k的star, 感興趣可以前去看看.


本問就討論這麼多內容,大家有什麼問題或好的想法歡迎在下方參與留言和評論.

本文作者: louis

本文連結: louiszhai.github.io/2017/01/13/…

參考文章

相關文章