margin: auto 的魔法世界
曹锋,医药支撑团队前端工程师。处女座,追求优雅的代码。
梦开始的地方
在 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)
最后
如果你觉得这篇内容对你挺有启发,我想邀请你帮我三个小忙:
点个「在看」,让更多的人也能看到这篇内容(喜欢不点在看,都是耍流氓 -_-)
欢迎加我微信「qianyu443033099」拉你进技术群,长期交流学习...
关注公众号「前端下午茶」,持续为你推送精选好文,也可以加我为好友,随时聊骚。