曹鋒,醫藥支撐團隊前端工程師。處女座,追求優雅的代碼。
夢開始的地方
在 CSS 的世界裏,各種居中問題可以說是時刻伴隨着我們,其中 margin: auto
必須是當之無愧的童年記憶,甚至到了如今我們已經掌握了各種 CSS 的奇技淫巧,這段樸實無華的代碼依舊佔有一席之地,四個字來評價:yyds!讓我們再來重溫一下這段經典代碼:
<style>
.parent-panel {
padding: 20px;
background-color: darksalmon;
}
.main-panel {
width: 60%;
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-tb
和 direction: ltr
的前提下,當我們給一個塊元素設置 margin-left: auto
( margin-right: auto
) 時,其計算值爲該塊元素的父級在水平方向上的可用剩餘空間,二者都設置了就均分剩餘空間,自然就讓該元素水平方向居中了。
上面說了讓固定寬度塊元素水平居中的方法,自然會想到居左和居右,居左就不用說了,居右該如何實現呢?按照上面對 margin
值爲 auto
的計算原理的解釋,我們不難想到一個解決方案:通過設置該元素的 margin-left: auto
讓其 margin-left
佔據父元素左側所有的可用剩餘空間,將其擠到父元素最右側從而實現居右效果,代碼如下:
<style>
.parent-panel {
padding: 20px;
background-color: darksalmon;
}
.main-panel {
width: 60%;
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-left: 2em;
}
上面的代碼已經基本完成了佈局要求,還差一個將輔助區域居右的該如何實現。一個思路是:讓「轉發」按鈕 flex-grow: 1
將輔助區域擠到右邊去,或者讓「投訴」按鈕 flex-grow: 1; text-align: right
也可以實現同樣效果。代碼如下:
.operate-panel .item.item-forward {
flex-grow: 1;
}
/* or */
.operate-panel .item.item-report {
flex-grow: 1;
text-align: right;
}
但是上面代碼有一個不好之處:元素寬度被拉伸了,這樣可能會給元素內部的佈局帶來影響,比如要求元素最大寬度不能超過 100px
,超出部分顯示省略號,所以我們的解決方法最好是不影響元素本身尺寸。
其實我們可以將左右兩個區域分別用一個容器包裹起來,然後對着兩個容器進行左右對齊佈局,但是這樣的話我們需要多寫一些爲了佈局而存在的 html
標籤,所以這個方案暫時不考慮。
那麼接下來就看看 margin: auto
在這裏能不能派上用場。我們先看看 margin: auto
在 Flexbox 佈局方式裏的 計算方式是如何的:
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">< 返回</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-left: 2em;
}
現在主要解決的問題是「標題」的定位,可以思考一下,在 flexbox 提供的現有對齊方式裏,我們找不到解決方案,除非我們改造 html
代碼將左側和右側區域分別用一個容器包裹起來,然後在父元素上 justify-content: space-between
。可以,但不優雅。
再看一遍要求:標題在 剩餘可用空間 內水平 居中 ,margin: auto
直呼我擅長。上一個例子是要求居右所以我們讓某一個關鍵元素的 margin-left: auto
或者 margin-right: auto
,這裏要求居中,那我們就給左右都 auto
均分剩餘空間:
.header-panel .item.item-title {
margin: 0 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 {
margin: 0 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-content
和 align-items
能實現的對齊效果使用 margin: auto
都可以實現,大家可以自己試一試其它效果,這裏就不再贅述。但是您可能要問了:既然人家 flexbox 自帶的都已經實現了這些對齊方式,這裏還有必要再用 margin: auto
來實現嘛。這就是我最開始講到的:
而且在一些特殊場景下
justify-content
的效果會有問題
來看一個場景:
要求如下:每一塊的寬度固定 width: 30%
不可伸縮,父元素剩餘可用空間均分到各塊之間作爲間隔,當數量過多寬度超出父級容器時滾動。我們可以寫出如下代碼:
<style>
.bottom-panel {
display: flex;
justify-content: space-evenly;
overflow: auto;
}
.bottom-panel > .item {
width: 30%;
flex-shrink: 0;
}
</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 {
width: 30%;
flex-shrink: 0;
}
</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 {
width: 30%;
flex-shrink: 0;
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源創計劃”,歡迎正在閱讀的你也加入,一起分享。