flex全稱Flexible Box模型,顧名思義就是靈活的盒子,不過一般都叫彈性盒子,所有PC端及手機端現代瀏覽器都支持,所以不用擔心它的兼容性,有了這玩意,媽媽再也不用擔心我們的布局。
先簡單介紹一下,要使用flex布局,需要先給一個容器元素設置display:flex讓它變成flex容器,然后其所有的直接子元素就變成flex子元素了,在flex里存在兩根軸,叫主軸和交叉軸,互相垂直,主軸默認水平,flex子元素默認會沿主軸排列,可以控制flex子元素在主軸上伸縮,主軸方向可以設置,相關的css屬性分為兩類,一類是給flex容器設置的,一類是給flex子元素設置的,本文在介紹一些典型場景實現的同時也會順帶講解部分屬性,當然更詳細的內容可以閱讀MDN上的教程。
單列布局
單列布局是最簡單的布局了,從上到下排列,如圖:

可以使用三個div來表示頭、內容和尾,然后把外層容器,即body設為flex容器,因為flex默認的主軸是水平的,我們需要把它設置為垂直的,然后再設置元素在交叉軸居中即可:

當然更常見的情況是內容高度不確定,這樣我們往往會希望在內容高度不滿一屏時底部內容挨着底邊,超過一屏時跟在最后,這首先需要容器元素有固定的高度,否則何來底邊,我們可以把html和body的高度都設為100%,然后去掉給content元素設置的高度,並給它添加一個帶高度的子元素:

接下來需要使用到flex-grow屬性,這個是flex子元素上的屬性,用來控制容器還有空間剩余時,flex子元素怎么進行擴展,默認值是0,也就是不擴展,子元素會顯示為它們默認的大小,這個所謂的默認大小分幾種情況:
1.如果子元素的另一個屬性flex-basis設置了不為auto的具體數值,那么無論元素有沒有設置具體大小都顯示為該屬性定義的尺寸;
2.如果子元素的flex-basis的值為auto(默認值),那么如果元素設置了具體的大小那么顯示為該設置的尺寸;
3.否則取決於元素內容的max-content大小;
當flex-grow設為一個正數時,那么各個子元素會按設置的份數來按比例分配剩余可用的空間,比如剩余空間為90px,三個子元素該屬性值都設為1,那么每個元素將在原來大小的基礎上加上90/3=30px。
根據上述原理,我們只需要給content元素的flex-grow屬性設為1即可,其他都是0,所以剩余空間將全給content元素:

這樣內容不足時底部就可以挨着底邊了,但是當內容過多,超過一屏時:

可以看到頭和尾都沒了,這是因為flex-shrink的原因,這個也是flex子元素上的屬性,用來控制當子元素的尺寸之和已經超過容器了要怎么收縮元素,默認值為1,就是按比例減去要收縮的空間,理論上是這樣,但實際上並沒有這么簡單,接下來簡單測試一下:
容器元素body為800px高,上中下高度分別為100+1000+100=1200px,根據1:1:1的flex-shrink計算總權重為1*100+1*1000+1*100=1200,子元素總高度超過容器400px,這多出的要按的比例從各自高度中減去,具體為:
(400*1*100)/1200=33.33px
(400*1*1000)/1200=333.33px
(400*1*100)/1200=33.33px
,也就是分別都減去上述值,減完后理論上各自的高度變成了66.67px、666.67.67px、66.67px,但是實際上:

可以看到頭和尾都變成了0,內容高度沒有變,這是為啥呢?上面我們提到了max-content,同樣,這里對應着min-content的概念,雖然正常來說應該變成我們計算出的尺寸才對,但是減少到元素內容的min-content后它就不會再變小了,content元素有個高度為1000的子元素,這個高度就是它的min-content,所以它不會縮小,它一個元素就比容器元素高了,再加上頭和尾因為都沒有內容,所以雖然理論上它們不是為0的,但是為了更好的顯示效果,瀏覽器就直接把它們減少到0了,我們可以隨便給頭和尾加一點文字,文字的高度就會變成它們的min-content,它們的高度也就不會變成0:

所以這就意味着不要想着去精確計算,把它交給瀏覽器,瀏覽器會給你以最好的方式呈現。
那么解決頭和尾不要消失的問題很簡單,可以給它們也加個固定高度的子元素,但是最簡單的方法是把它們的flex-shrink設為0,也就是不收縮:

這樣就實現我們的需求了。
經典導航欄

如圖所示是一個經典的網站導航欄的布局,logo和導航在左,用戶信息在右,不用flex可能會使用浮動,上圖使用浮動還好,但是如果右邊是兩個塊,那么右邊浮動的元素的顯示順序和書寫順序要不一致才行,或者再用一個元素包裹一下,使用flex則沒有這種煩惱。
該場景可以使用一個容器來包裹左邊的logo和導航,再設置justify-content:space-between來實現,但是有個小技巧可以不用這個包裹元素,就是利用margin的auto值,回憶一下我們以前水平居中都是怎么做的,是不是這樣margin:0 auto,margin-left和margin-right的默認值是0,如果設置為auto,將會根據剩余可用空間來計算,這也是為什么能水平居中,因為左右都想盡量多,那么就只能平分了,對於本示例,我們只給用戶名flex子元素設置margin-left:auto,那么剩余空間將全部給它,也就相當於把用戶塊擠到右邊去了:

隔行交叉顯示

有時候為了不讓布局太單調,即使一個列表是同類數據,展示上也會做成上述隔行交叉的樣式,這個使用flex可以輕松的做到,給2n的行設置flex-direction: row-reverse即可讓偶數行的主軸方向由默認的從左向右變成從右向左:

此外也可以使用order屬性,這個屬性可以讓flex子元素按order的數值大小來排序顯示,我們可以默認左邊的設為2,右邊的設為3,然后在偶數行再給右邊的設為1,自然就跑到前面去了:

網格布局
此網格非grid布局,雖然網格列表用grid是最好的,但是本文的主角是flex,假設我們要實現下面這樣一個列表:

上述列表對flex來說是不擅長的,因為要帶間距,所以不能簡單的把子元素寬度設為25%,否則再加上外邊距,一行肯定顯示不下四個,那你可能會想,那我寬度就少一點好了,比如設為20%,然后允許擴展,即flex-grow:1,那樣不就可以把減去子元素寬度及外邊距還剩下的空間再還給子元素了嗎,試試看:

可以看到前面一切正常,但是最后一行因為只有一個元素,且設置了允許擴展,所以它被拉滿整行了,這種效果顯然不是我們要的。
其實我們可以使用內邊距來做間距,設置一下子元素的box-sizing:border-box,讓內邊距包含在寬度內,這樣就可以放心的把子元素的寬度設為25%了,當然這樣的缺點是里面得再嵌套一個元素用來作為實際的內容容器。

聖杯布局

所謂聖杯布局如上所示,頭尾高度固定,寬度占滿,中間的內容部分分為三列,兩側寬度固定,高度占滿,中間的內容部分隨着瀏覽器寬度變化,其實就是我們上面講過的【單列布局】的中間部分變成三列而已,實現完全沒有啥特別的,以【單列布局】為基礎,給content添加三個子元素,兩側定寬,並且不允許收縮,中間允許擴展即可:

垂直居中
不知道各位最開始用flex是為什么,反正筆者就是沖着垂直居中去的,用它實在是太簡單了,之前還考慮是不是定高呀,用什么定位呀,用flex就是兩步,一讓父元素變成彈性盒子,二設置交叉軸的元素排布方式為居中就完事了:

如果還需要水平居中的話就再給容器元素設置主軸的排列方式為justify-content:center,現在連讓文字居中我都是用flex,無情的拋棄了text-align和line-height。
高度自動對齊
有些時候同一列的元素為了美觀我們希望他們的高度是一樣的,如果內容固定不變當然可以直接寫死高度,但如果可變的話就不能寫死了:

這個場景使用flex完全不需要額外設置什么屬性,只要給容器元素設置display:flex即可,因為flex容器有個屬性align-items,用來設置flex子元素在交叉軸上如何對齊,默認值為stretch,即如果flex子元素未設置高度,那么將占滿整個容器的高度,因為我們並沒有給容器設置高度,所以容器的高度就是所有flex子元素里最大的高度。

小結
本文以標題黨的名義總結了部分常見布局使用flex的實現,要靈活使用flex還是需要理解它的一些屬性的意義,此外也需要知道flex的邊界在哪,哪些是它不擅長的。
本文總結的難免會有不全,或者有更好的實現,歡迎討論。
