雖然使用 CSS 建立居中效果需要耍一些花招,特別是垂直居中效果,但我認為由此生出的詆譭,對於 CSS 則是不公平的。實際上我們有太多的方式使用 CSS 建立居中效果了,而且作為一名前端開發者,你真的有必要對其中的原理了解一二。
寫作本文的目的不是為了向各位解釋這些方法的工作原理,而是介紹將這些方法編寫為 Sass mixin 的方式,繼而將它們複用到各類專案中。如果你還不熟悉使用 CSS 建立居中效果的方法,我建議你仔細閱讀以下這篇文章:Centering In CSS: A Complete Guide。
總體概述
本文將會專注於解決子元素居中於父類容器的問題,就實踐經驗來說,這也是最常使用到的居中效果。當你請教別人 CSS 中和居中效果相關的問題時,他們往往會反問你:你知道元素具體的寬高嗎?之所以會有這樣的反問,是因為如果知道元素的寬高,那麼最好的解決方案就是使用 CSS transform 屬性。雖然該屬性在瀏覽器中的支援度稍低,但卻有著高度靈活的特性;如果因為瀏覽器相容性令你不能使用 CSS transform 屬性,或者也不知道元素的寬高,那麼實現居中效果的最簡單方法就是使用負向 margin。
我們今天要建立的 Sass mixin 就是基於上述的方法:將元素的左上角絕對定位到容器的中心位置,然後為 mixin 新增兩個可選引數,分別代表元素的寬高,如果傳遞了引數,那麼就使用負向 margin 的方法實現居中;如果沒有傳遞引數,就使用 CSS transform 的方法。
當我們的 Sass mixin 建立成功後,基本的使用方式如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 |
/** * 為子元素設定定位上下文 */ .parent { position: relative; } /** * 讓子元素絕對居中於父容器 * 沒有向 Sass mixin 傳遞引數,使用 CSS transform 屬性實現居中效果 */ .child-with-unknown-dimensions { @include center; } /** * 讓子元素絕對居中於父容器 * 向 Sass mixin 傳遞了寬度,所以就使用負向 margin 處理水平位置, * 使用 CSS transform 處理垂直位置 */ .child-with-known-width { @include center(400px); } /** * 讓子元素絕對居中於父容器 * 向 Sass mixin 傳遞了高度,所以就使用負向 margin 處理垂直位置, * 使用 CSS transform 處理水平位置 */ .child-with-known-height { @include center($height: 400px); } /** * 讓子元素絕對居中於父容器 * 向 Sass mixin 傳遞了高度和寬度,所以就使用負向 margin 處理水平和垂直位置 */ .child-with-known-dimensions { @include center(400px, 400px); } |
上述 Sass 程式碼經過編譯之後,輸出結果如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
.parent { position: relative; } .child-with-unknown-dimensions { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); } .child-with-known-width { position: absolute; top: 50%; left: 50%; margin-left: -200px; width: 400px; transform: translateY(-50%); } .child-with-known-height { position: absolute; top: 50%; left: 50%; transform: translateX(-50%); margin-top: -200px; height: 400px; } .child-with-known-dimensions { position: absolute; top: 50%; left: 50%; margin-left: -200px; width: 400px; margin-top: -200px; height: 400px; } |
還不錯,就是看起來有點囉嗦,不過鑑於是用來做 demo 的,也不必太過強求了。
建立 mixin
思路屢清楚了,下面開工!根據上面的程式碼片段,我們已經知道了這個 mixin 的主要特徵:接收兩個可選的引數,用來表示元素的寬高($width
和 $height
)。
1 2 3 4 5 6 7 8 9 10 11 |
/// Horizontal, vertical or absolute centering of element within its parent /// If specified, this mixin will use negative margins based on element's /// dimensions. Else, it will rely on CSS transforms which have a lesser /// browser support but are more flexible as they are dimension-agnostic. /// /// @author Hugo Giraudel /// /// @param {Length | null} $width [null] - Element width /// @param {Length | null} $height [null] - Element height /// @mixin center($width: null, $height: null) { .. } |
然後,由分析知,要實現居中必須讓元素絕對定位:
1 2 3 4 5 |
@mixin center($width: null, $height: null) { position: absolute; top: 50%; left: 50%; } |
在這裡讓我們暫停一下,深入分析後續邏輯的層次:
width | height | solution |
---|---|---|
null | null | translate |
defined | defined | margin |
defined | null | margin-left + translateY |
null | defined | margin-right + translateX |
秀程式碼:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
@mixin center($width: null, $height: null) { position: absolute; top: 50%; left: 50%; <a href="http://www.jobbole.com/members/jinyi7016">@if</a> not $width and not $height { // Go with `translate` } @else if $width and $height { // Go width `margin` } @else if not $height { // Go with `margin-left` and `translateY` } @else { // Go with `margin-top` and `translateX` } } |
通過上面的程式碼,我們已經搭好了 mixin 的骨架,只需要再新增上具體的邏輯程式碼即可:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
@mixin center($width: null, $height: null) { position: absolute; top: 50%; left: 50%; <a href="http://www.jobbole.com/members/jinyi7016">@if</a> not $width and not $height { transform: translate(-50%, -50%); } @else if $width and $height { width: $width; height: $height; margin: -($width / 2) #{0 0} -($height / 2); } @else if not $height { width: $width; margin-left: -($width / 2); transform: translateY(-50%); } @else { height: $height; margin-top: -($height / 2); transform: translateX(-50%); } } |
注意!上面程式碼中的
#{0 0}
實際上一種容錯措施,如果直接使用0 0
的話,Sass 解析器會嘗試進行數值運算(在這裡會自動進行0 -($height / 2)
的數學運算),進而導致我們得到margin: mt 0 ml;
而不是想要得到的margin: mt 0 0 ml;
。
深入淺出
基本的功能實現後,我們還可以新增更多的特性,比如新增 @support
來檢查瀏覽器對 CSS transform 的支援度,進而可以根據 CSS transform 的支援度輸出相應的條件樣式。此外,我們還可以更嚴謹地去測試出入的引數是否是有效數值……
使用 Flexbox
看到 Flexbox 這個詞是不是就很興奮啊,少年!確實,使用 Flexbox 確實是最簡單的方式,它和前面方法主要的差別在於,使用 Flexbox 需要為父容器設定相關樣式,而使用前面的方法則主要是為子元素設定相關樣式(當然,父容器需要被設定為除 static
之外的任意 position
)。
使用 Flexbox 實現子元素的居中效果,只需三行程式碼:
1 2 3 4 5 |
@mixin center-children { display: flex; justify-content: center; align-items: center; } |
由於 Flexbox 還是比較新的屬性,那麼新增上相關的瀏覽器字首的話,會讓它擁有更廣泛的相容性。
1 2 3 |
.parent { @include center-children; } |
正如你料想的那樣,就這麼簡單我們就實現了:
1 2 3 4 5 |
.parent { display: flex; justify-content: center; align-items: center; } |
總結
我們就想要一個簡短的 mixin 讓元素在父容器中居中,我們做到了,而且做的更好。它不僅僅簡單易用無副作用,而且提供了良好的開發介面。