Cruyun's Blog


Talk is cheap, show you my code


如何实现一个子元素居中,另一个子元素在边缘(上下左右)

先行知识:auto margins & flexbox

1. How does margin auto work?

我们经常使用margin: 0 auto 使元素水平居中,那么 auto margin 是如何起作用的呢? 字面上来看,设定 auto margin 即让浏览器决定 margin,浏览器会计算父元素和子元素的宽度,将剩余的宽度等分,分别给左右 margin,于是子元素水平居中。

根据 CSS specification Calculating widths and margins for Block-level, non-replaced elements in normal flow :

if both ‘margin-left’ and ‘margin-right’ are ‘auto’, their used values are equal. This horizontally centers the element with respect to the edges of the containing

下面有个有趣的情况,如果你的父容器是 display: flex, 那么margin: auto auto(或者 margin: auto ) 的子元素将会垂直水平居中,无论是行内元素或块元素(an inline or block element.)

当然,如果使用 flexbox 的话,我们无需设置auto margins 实现水平垂直居中,用align-items: center;justify-content: center; 即可。

2. flexbox 的各个常用的属性可以参阅A Complete Guide to Flexbox


回到正文,下面主要介绍以下方法:

  • CSS Positioning
  • Flexbox with Invisible DOM Element
  • Flexbox with Invisible Pseudo-Element
  • Flexbox with flex: 1
  • Add Justify-content: Space-between & Invisible Flex Item (DOM element)
  • CSS Grid Layout
1
2
3
------------------
ABC D
------------------

Method #1: CSS Positioning Properties

父元素 position: relative,item D position: absolute,虽然子元素 D 会由此脱离文档流,但仍在最近的父容器的范围内,再使用 CSS 偏移的属性移动元素到目标位置。

demo戳这里

Method #2: Flex Auto Margins & Invisible Flex Item (DOM element)

Flexible Box Layout Module - 8.1. Aligning with auto margins

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.

通过设置 auto margin ,添加一个不可见(visibility: hidden)的 flex item 实现布局。新的 item 与 item D相同,并放置在另一端(左边缘)。

更具体地说,因为flexbox 对齐是基于剩余空间的分布,所以为了保持三个中间的 item 水平居中。新 item 的宽度必须与现有 item D 的宽度相同,否则中间 item 将不会精确居中。

示例代码如下,demo 戳这里

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
li:first-child {
margin-right: auto;
visibility: hidden;
}
li:last-child {
margin-left: auto;
background: #ddd;
}
ul {
padding: 0;
margin: 0;
display: flex;
flex-direction: row;
justify-content: center;
align-items: center;
}
li {
display: flex;
margin: 1px;
padding: 5px;
background: #aaa;
}
<ul>
<li>D</li><!-- new; invisible spacer item -->
<li>A</li>
<li>B</li>
<li>C</li>
<li>D</li>
</ul>

与应用于flex 父容器的justify-content不同,auto margins 用于flex items,通过占用指定方向上的所有可用空间起作用。

Method #3: Flex Auto Margins & Invisible Flex Item (pseudo-element)

这个方法与上一个类似,只是将复制 item D 改为使用伪元素去创造 D 的副本。缺点是 item D 必须是不可变的确定内容,优点是在代码上更加简洁易懂。

示例代码如下,demo 戳这里

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
ul::before {
content:"D";
margin: 1px auto 1px 1px;
visibility: hidden;
padding: 5px;
background: #ddd;
}
// 其余 CSS 代码与上一方法相同
// HTML
<ul>
<li>A</li>
<li>B</li>
<li>C</li>
<li>D</li>
</ul>

Method #4: Add flex: 1 to left and right items

flex 规定了弹性元素如何伸长或缩短以适应flex容器中的可用空间。

不像方法#2或#3,需要在明确 item D 的内容, 确保左右项目的宽度相等以保持平衡,本方法只需给边缘 item 设 flex:1, 左右两边的子元素将获得相同的宽度(它们将均匀分配可用空间)使得中心元素将始终完美居中。

示例代码如下,demo 戳这里

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/* CSS */
.flex-container {
display: flex;
flex-direction: row;
background: lightblue;
width: 300px;
min-height: 80px;
justify-content: center;
align-items: center;
}
.item {
flex: 1;
}
/* HTML */
<div class="flex-container">
<div class="item">left</div>
<div>center</div>
<div class="item"></div>
</div>

该方法只适用于按行排列的情况。

Method #5: Add Justify-content: Space-between & Invisible Flex Item (DOM element)

justify-content属性定义了浏览器如何分配顺着父容器主轴的弹性元素之间及其周围的空间。

justify-content: space-between; 均匀排列每个元素,首个元素放置于起点,末尾元素放置于终点。

示例代码如下,demo 戳这里

1
2
3
4
5
6
7
8
9
10
11
12
.container {
display: flex;
flex-direction: column;
background: lightblue;
width: 300px;
height: 100px;
justify-content: space-between;
align-items: center;
}
.item {
background-color: white;
}
1
2
3
4
5
<div class="container">
<div class="item">left</div>
<div class="item">center</div>
<div class=""></div>
</div>

Method #6: CSS Grid Layout

这可能是最干净,最有效的方法。不需要绝对定位,虚假元素或其他hackery。

只需创建一个包含多列的网格。然后将项目放在中间和结束列中。基本上,只需将第一列留空即可。

示例代码如下,demo 戳这里

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
ul {
display: grid;
grid-template-columns: 1fr repeat(3, auto) 1fr;
grid-column-gap: 5px;
justify-items: center;
}
li:nth-child(1) { grid-column-start: 2; }
li:nth-child(4) { margin-left: auto; }
ul { padding: 0; margin: 0; list-style: none; }
li { padding: 5px; background: #aaa; }
p { text-align: center; }
<ul>
<li>A</li>
<li>B</li>
<li>C</li>
<li>D</li>
</ul>

小结

这种情况实际上在导航栏等组件中是有可能经常出现的。当然解法不止以上几种,以上仅仅是我总结的几种方法。

如果横向排列,我推荐使用方法 #4,纵向排列推荐使用方法 #5。将来 CSS Grid得到更多的兼容支持的时候,也许是最简洁的方法。


参考及推荐阅读: