margin: auto 的魔法世界

曹鋒,醫藥支撐團隊前端工程師。處女座,追求優雅的代碼。

夢開始的地方

在 CSS 的世界裏,各種居中問題可以說是時刻伴隨着我們,其中 margin: auto 必須是當之無愧的童年記憶,甚至到了如今我們已經掌握了各種 CSS 的奇技淫巧,這段樸實無華的代碼依舊佔有一席之地,四個字來評價:yyds!讓我們再來重溫一下這段經典代碼:

<style>
 .parent-panel {
    padding20px;
    background-color: darksalmon;
  }
  .main-panel {
    width60%;
    margin: auto;
    background-color: blanchedalmond;
    text-align-last: justify;
  }
</style>

<div class="parent-panel">
 <div class="main-panel">我居中了</div>
</div>

效果如下:

margin: auto 解決了 讓一個 正常佈局流(normal flow)固定寬度 元素 水平 居中 的問題,其原理就是:在 writing-mode: horizontal-tbdirection: ltr 的前提下,當我們給一個塊元素設置 margin-left: auto ( margin-right: auto ) 時,其計算值爲該塊元素的父級在水平方向上的可用剩餘空間,二者都設置了就均分剩餘空間,自然就讓該元素水平方向居中了。

上面說了讓固定寬度塊元素水平居中的方法,自然會想到居左和居右,居左就不用說了,居右該如何實現呢?按照上面對 margin 值爲 auto 的計算原理的解釋,我們不難想到一個解決方案:通過設置該元素的 margin-left: auto 讓其 margin-left 佔據父元素左側所有的可用剩餘空間,將其擠到父元素最右側從而實現居右效果,代碼如下:

<style>
 .parent-panel {
    padding20px;
    background-color: darksalmon;
  }
  .main-panel {
    width60%;
    margin-left: auto;
    background-color: blanchedalmond;
    text-align-last: justify;
  }
</style>

<div class="parent-panel">
 <div class="main-panel">我現在居右了</div>
</div>

效果如下:

通過上面兩個例子可以看到, margin 值爲 auto 可以很方便的控制一個固定寬度的塊元素在水平方向上的對齊方式,但其實能做的事情很有限,主要是因爲我們對該元素的約束很多: 固定寬度塊元素水平方向 ,而且因爲本身是塊元素,所以在正常文檔流中該行只會存在一個塊元素,那麼我們如何控制一些更爲複雜的場景下元素的對其方式呢,比如垂直方向、多元素的場景等,大家一定都已經想到了,那就是 Flexbox 佈局方式了。

Flexbox 帶來的更多可能

Flexbox 佈局想必大家都已經很熟悉並且在實際開發中大量使用了,它的出現爲開發者提供了更優雅的佈局方案,甚至是以前無法單獨使用 CSS 解決的問題,具體的一些常用場景可以查看 這裏(https://github.com/philipwalton/solved-by-flexbox) ,我們這裏就不展開討論各種佈局方案,主要還是看看 Flexbox 遇到 margin: auto 會有哪些有趣的故事。

Flexbox 佈局本身就自帶很多屬性用來控制子元素在 主軸(main axis)交叉軸(cross axis) 上的對其方式: align-items align-self justify-content 等,想必大家都已經大量使用過了,但是對於子元素在 主軸 上對其方式的控制,沒有一個 justify-self 用來讓單個元素可以對自己進行特殊處理。而且在一些特殊場景下 justify-content 的效果會有問題,下面都會一一講到。接下來讓我們看看一些實際佈局效果該如何實現。

左右對齊

看看下面這種非常常見的佈局方式:

佈局要求如下:所有子元素垂直方向居中對齊,水平方向上分成兩個區域,左側的「一鍵三連」主操作區,右側的一些輔助操作區域,並且我們要求編寫儘可能簡潔的 html 代碼,僞代碼如下:

<ul class="operate-panel">
  <!-- 主操作區域 -->
  <li class="item">點贊</li>
  <li class="item">投幣</li>
  <li class="item">收藏</li>
  <li class="item item-forward">轉發</li>
  
  <!-- 輔助操作區域 -->
  <li class="item item-report">投訴</li>
  <li class="item">筆記</li>
  <li class="item">更多操作</li>
</ul>

我們首先想到的肯定是使用 Flexbox 進行佈局,代碼如下:

.operate-panel {
  display: flex;
  align-items: center;
}
.operate-panel .item + .item {
  margin-left2em;
}

上面的代碼已經基本完成了佈局要求,還差一個將輔助區域居右的該如何實現。一個思路是:讓「轉發」按鈕 flex-grow: 1 將輔助區域擠到右邊去,或者讓「投訴」按鈕 flex-grow: 1; text-align: right 也可以實現同樣效果。代碼如下:

.operate-panel .item.item-forward {
  flex-grow1;
}
/* or */
.operate-panel .item.item-report {
  flex-grow1;
  text-align: right;
}

但是上面代碼有一個不好之處:元素寬度被拉伸了,這樣可能會給元素內部的佈局帶來影響,比如要求元素最大寬度不能超過 100px ,超出部分顯示省略號,所以我們的解決方法最好是不影響元素本身尺寸。

其實我們可以將左右兩個區域分別用一個容器包裹起來,然後對着兩個容器進行左右對齊佈局,但是這樣的話我們需要多寫一些爲了佈局而存在的 html 標籤,所以這個方案暫時不考慮。

那麼接下來就看看 margin: auto 在這裏能不能派上用場。我們先看看 margin: autoFlexbox 佈局方式裏的 計算方式是如何的:

Auto margins on flex items have an effect very similar to auto margins in block flow:

  • During calculations of flex bases and flexible lengths, auto margins are treated as 0.
  • Prior to alignment via justify-content and align-self, any positive free space is distributed to auto margins in that dimension.
  • Overflowing boxes ignore their auto margins and overflow in the end direction.

根據規範的定義,簡單來說,在 flex items 上定義 margin: auto 的效果和上面講到的在塊元素上效果類似:佔用父級剩餘可用空間,但是有些不同的是, flex items 上的 margin: auto 不僅對水平方向有效,對垂直方向同樣有效,OMG 用它用它用它!!! 直接看代碼:

.operate-panel .item.item-forward{
 margin-right: auto;
}
/* or */
.operate-panel .item.item-report {
  margin-left: auto;
}

一個字:非常的優雅!這個問題就非常優雅的解決了,下一位。

帶操作的頁面頂部欄

標題有點繞,直接看效果圖:

要求如下:左側「返回」按鈕居左,右側操作區域居右,標題在 剩餘可用空間 內水平 居中,所有子元素垂直方向居中。先看看 html 代碼:

<ul class="header-panel">
  <li class="item">&lt; 返回</li>
  <li class="item item-title">我是標題</li>
  <li class="item">清單</li>
  <li class="item">搜索</li>
  <li class="item">發佈</li>
</ul>

不出意料我們還是使用 flexbox 進行佈局,主要的 CSS 代碼如下:

.header-panel {
  display: flex;
  align-items: center;
}
.header-panel .item + .item {
  margin-left2em;
}

現在主要解決的問題是「標題」的定位,可以思考一下,在 flexbox 提供的現有對齊方式裏,我們找不到解決方案,除非我們改造 html 代碼將左側和右側區域分別用一個容器包裹起來,然後在父元素上 justify-content: space-between 。可以,但不優雅。

再看一遍要求:標題在 剩餘可用空間 內水平 居中margin: auto 直呼我擅長。上一個例子是要求居右所以我們讓某一個關鍵元素的 margin-left: auto 或者 margin-right: auto ,這裏要求居中,那我們就給左右都 auto 均分剩餘空間:

.header-panel .item.item-title {
  margin0 auto;
}

其實這裏還有一個設置方法:給「標題」左側的「返回」加一個 margin-right: auto ,再給「標題」右側的「清單」加一個 margin-left: auto ,效果一樣。可以,但沒必要。這個問題到此也就完美解決了,下一位。

margin: auto :我要篡位

經歷了上面的代碼,相信大家已經對 margin: auto 這段樸實無華的代碼肅然起敬,請把「膨脹」打在公屏上。 margin: auto 現在很膨脹,塔門說:誒,你 justify-content 不好使,也不要給我說什麼 align-items align-self ,老夫搞佈局就是一把 margin: auto 複製!粘貼!哪裏不齊貼哪裏!那麼事實真是如此嗎?我們今天有幸請到了 margin: auto 馬老師本碼,來爲我們講解一下,以下是來自現場的文字報道。

<ul class="flex-panel">
  <li class="item"></li>
  <li class="item"></li>
  <li class="item"></li>
</ul>

.flex-panel {
  display: flex;
}

/* align-items: center */
.flex-panel > .item {
  margin: auto 0;
}

/* justify-content: center */
.flex-panel > .item:first-child {
  margin-left: auto;
}
.flex-panel > .item:last-child {
  margin-right: auto;
}

/* justify-content: space-around */
.flex-panel > .item {
  margin0 auto;
}

/* justify-content: space-between */
.flex-panel > .item + .item {
  margin-left: auto;
}

/* justify-content: space-evenly */
.flex-panel > .item {
  margin-left: auto;
}
.flex-panel > .item:last-child {
  margin-right: auto;
}

以上只列出了部分實現,其實所有 justify-contentalign-items 能實現的對齊效果使用 margin: auto 都可以實現,大家可以自己試一試其它效果,這裏就不再贅述。但是您可能要問了:既然人家 flexbox 自帶的都已經實現了這些對齊方式,這裏還有必要再用 margin: auto 來實現嘛。這就是我最開始講到的:

而且在一些特殊場景下 justify-content 的效果會有問題

來看一個場景:

底部欄空間充足
底部欄空間不足滾動

要求如下:每一塊的寬度固定 width: 30% 不可伸縮,父元素剩餘可用空間均分到各塊之間作爲間隔,當數量過多寬度超出父級容器時滾動。我們可以寫出如下代碼:

<style>
 .bottom-panel {
    display: flex;
    justify-content: space-evenly;
    overflow: auto;
  }
  .bottom-panel > .item {
    width30%;
    flex-shrink0;
  }
</style>

<ul class="bottom-panel">
  <li class="item"></li>
  <li class="item"></li>
  <li class="item"></li>
</ul>

這段代碼在子元素數量較少不會超出父級容器時表現非常完美,但是,如果子元素數量超過 3 個時,所有子元素寬度之和大於 100% 必然超出父元素,此時的效果就你太正常了,左側的部分子元素整個或者部分會被隱藏,而且通過滾動父元素也無法令其顯示,如下:

<style>
 .bottom-panel {
    display: flex;
    justify-content: space-evenly;
    overflow: auto;
  }
  .bottom-panel > .item {
    width30%;
    flex-shrink0;
  }
</style>

<ul class="bottom-panel">
  <li class="item"></li>
  <li class="item"></li>
  <li class="item"></li>
  <li class="item"></li>
  <li class="item"></li>
  <li class="item"></li>
</ul>

底部欄部分被隱藏

這個問題在 justify-content: center justify-content: space-around 的時候同樣存在,規範裏定義了一個 safe 來解決這個問題,但是很遺憾目前瀏覽器支持情況還非常差。

margin: auto 總是會在你最需要的時候忽然出現。

再看一下上面提到的規範定義:

Overflowing boxes ignore their auto margins and overflow in the end direction.

在這種情況下 margin: auto 會選擇默默的消失,還你一份 love and peace

<style>
 .bottom-panel {
    display: flex;
    overflow: auto;
  }
  .bottom-panel > .item {
    width30%;
    flex-shrink0;
    margin-left: auto;
  }
  .bottom-panel > .item:last-child {
    margin-right: auto;
  }
</style>

<ul class="bottom-panel">
  <li class="item"></li>
  <li class="item"></li>
  <li class="item"></li>
  <li class="item"></li>
  <li class="item"></li>
  <li class="item"></li>
</ul>

總結

相信大家現在對 margin: auto 的認識和使用都有了更加深刻的印象,尤其和 flexbox 雙劍合璧會帶來很多美妙的魔法般的體驗,flexbox 本身就是非常強大的佈局方案,有了 margin: auto 的加持更是如虎添翼。文中如果有錯誤或者表達不妥的地方,歡迎大家拍磚助我進步。我本人對 CSS 非常熱愛,喜歡用優雅的方式實現頁面佈局,也非常歡迎大家一起來探討。

margin: auto ,永遠滴神!

參考資料

  • margin 系列之 keyword auto (https://www.ituring.com.cn/article/64580)
  • solved by flexbox (https://github.com/philipwalton/solved-by-flexbox)
  • Aligning with auto margins (https://www.w3.org/TR/css-flexbox-1/#auto-margins)
  • What's the difference between margin:auto and justify-content / align-items center? (https://stackoverflow.com/questions/44244549/whats-the-difference-between-marginauto-and-justify-content-align-items-cent)
  • In CSS Flexbox, why are there no “justify-items” and “justify-self” properties? (https://stackoverflow.com/questions/32551291/in-css-flexbox-why-are-there-no-justify-items-and-justify-self-properties/33856609#33856609)
  • Can't scroll to top of flex item that is overflowing container (https://stackoverflow.com/questions/33454533/cant-scroll-to-top-of-flex-item-that-is-overflowing-container)
  • Overflow Alignment: the safe and unsafe keywords and scroll safety limits (https://www.w3.org/TR/css-align-3/#overflow-values)


順手點“在看”,每天早下班;轉發加關注,共奔小康路~

加站長好友可進微信羣,跟衆多大佬一起交流技術!

本文分享自微信公衆號 - 1024譯站(trans1024)。
如有侵權,請聯繫 [email protected] 刪除。
本文參與“OSC源創計劃”,歡迎正在閱讀的你也加入,一起分享。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章