一句话说清 justify-content 和 align-items
在 flex 容器中,justify-content 沿主轴(横跨一行)分布元素,align-items 在交叉轴(行的上下方向)上对齐元素。两者都写在容器上:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Center both ways</title>
<style>
.box {
display: flex;
justify-content: center; /* horizontal, in a row */
align-items: center; /* vertical, in a row */
height: 140px;
background: #e5e7eb;
}
.item { background: #2563eb; color: white; padding: 12px 18px; }
</style>
</head>
<body>
<div class="box"><div class="item">Centered</div></div>
</body>
</html>
方块在两个方向上都居中了:到起始边和末尾边的距离相同,到顶部和底部的距离也相同。去掉 height: 140px,align-items: center 就无事可做了,因为容器会缩到和元素本身一样高。
实测每个 justify-content 取值
下面每一行都是同样的三个元素,只是 justify-content 不同。每行上方标签里的数字是浏览器留下的间距:第一个元素之前、元素之间,以及最后一个元素之后。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>justify-content values</title>
<style>
body { font-family: system-ui, sans-serif; font-size: 13px; }
.row { display: flex; width: 420px; background: #e5e7eb; margin: 2px 0 10px; }
.row div { width: 60px; height: 28px; background: #2563eb; }
.row div:nth-child(2) { background: #16a34a; }
.row div:nth-child(3) { background: #f59e0b; }
</style>
</head>
<body>
<div id="list"></div>
<script>
const values = ['flex-start', 'center', 'flex-end', 'space-between', 'space-around', 'space-evenly'];
const list = document.getElementById('list');
values.forEach((v) => {
const label = document.createElement('code');
const row = document.createElement('div');
row.className = 'row';
row.style.justifyContent = v;
row.innerHTML = '<div></div><div></div><div></div>';
list.append(label, row);
const b = row.getBoundingClientRect();
const r = [...row.children].map((c) => c.getBoundingClientRect());
const gaps = [r[0].left - b.left, r[1].left - r[0].right, r[2].left - r[1].right, b.right - r[2].right];
label.textContent = 'justify-content: ' + v + ' gaps: ' + gaps.map(Math.round).join(' / ');
});
</script>
</body>
</html>
容器宽 420px,元素占 180px,所以有 240px 的剩余空间。它被分配到哪里:
| 值 | 剩余空间的去向 | 演示中的间距 |
|---|
flex-start(以及 start) | 全部在元素之后 | 0 / 0 / 0 / 240 |
center | 一半在前,一半在后 | 120 / 0 / 0 / 120 |
flex-end(以及 end) | 全部在元素之前 | 240 / 0 / 0 / 0 |
space-between | 只在元素之间 | 0 / 120 / 120 / 0 |
space-around | 每个元素两侧相等,所以两端只有一半 | 40 / 80 / 80 / 40 |
space-evenly | 每个间距都相等,包括两端 | 60 / 60 / 60 / 60 |
flex-start 跟随 flex 方向,start 跟随书写方向。只有在 row-reverse 或 column-reverse 中它们才有区别,此时 flex-start 位于反转后的那一端。
每个 align-items 取值
align-items 作用于行的交叉方向。下面的元素高度各不相同,这样你就能看出每个取值如何处理它们:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>align-items values</title>
<style>
body { font-family: system-ui, sans-serif; font-size: 13px; }
.grid { display: flex; flex-wrap: wrap; gap: 16px; }
.row { display: flex; gap: 4px; width: 170px; height: 100px; background: #e5e7eb; }
.row div { width: 50px; background: #2563eb; color: white; }
.row div:nth-child(1) { font-size: 26px; }
.row div:nth-child(2) { background: #16a34a; padding-top: 20px; }
.row div:nth-child(3) { background: #f59e0b; }
</style>
</head>
<body>
<div class="grid" id="grid"></div>
<script>
['stretch', 'flex-start', 'center', 'flex-end', 'baseline'].forEach((v) => {
const cell = document.createElement('div');
cell.innerHTML = '<code>align-items: ' + v + '</code><div class="row" style="align-items: ' + v + '"><div>Ag</div><div>Ag</div><div>Ag</div></div>';
document.getElementById('grid').append(cell);
});
</script>
</body>
</html>
| 值 | 在交叉轴上的效果 |
|---|
stretch(默认值) | 没有设置高度的元素会拉伸填满容器 |
flex-start(以及 start) | 元素以自身高度靠顶部放置 |
center | 元素以自身高度居中 |
flex-end(以及 end) | 元素靠底部放置 |
baseline | 元素移动,使第一行文字共用一条基线 |
在 baseline 的盒子里,三个 “Ag” 标签位于同一条线上,尽管第一个字更大、第二个上方还有内边距。当一行里混合了大标题和小字,或者标签和输入框时,就该用这个值。
点击对齐
点击 3 × 3 网格中的任意单元格。方块会移到那里,下方会打印出把它放到那里的 CSS:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Alignment picker</title>
<style>
body { font-family: system-ui, sans-serif; font-size: 14px; }
.wrap { display: flex; gap: 16px; align-items: flex-start; }
.picker { display: grid; grid-template-columns: repeat(3, 28px); gap: 4px; }
.picker button { height: 28px; border: 1px solid #111827; background: white; cursor: pointer; }
.picker button[aria-pressed="true"] { background: #2563eb; }
#stage { display: flex; width: 260px; height: 150px; background: #e5e7eb; }
.item { background: #16a34a; color: white; padding: 10px 14px; }
pre { background: #f3f4f6; padding: 8px; }
</style>
</head>
<body>
<div class="wrap">
<div class="picker" id="picker"></div>
<div id="stage"><div class="item">Box</div></div>
</div>
<pre id="out"></pre>
<script>
const spots = ['flex-start', 'center', 'flex-end'];
const stage = document.getElementById('stage');
const pick = (row, col, btn) => {
stage.style.alignItems = spots[row];
stage.style.justifyContent = spots[col];
document.querySelectorAll('.picker button').forEach((b) => b.setAttribute('aria-pressed', b === btn));
document.getElementById('out').textContent =
'.stage {\n display: flex;\n justify-content: ' + spots[col] + ';\n align-items: ' + spots[row] + ';\n}';
};
spots.forEach((_, row) => spots.forEach((_, col) => {
const btn = document.createElement('button');
btn.setAttribute('aria-label', 'row ' + (row + 1) + ', column ' + (col + 1));
btn.addEventListener('click', () => pick(row, col, btn));
document.getElementById('picker').append(btn);
}));
pick(1, 1, document.querySelectorAll('.picker button')[4]);
</script>
</body>
</html>
选择器的行改变 align-items,列改变 justify-content。中间那一格就是经典的“让 div 居中”。
在列布局中,两者互换
justify-content 始终跟随主轴。设置 flex-direction: column 后,主轴变成垂直方向,所以 justify-content 上下移动元素,align-items 左右移动元素:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Column axes</title>
<style>
.col {
display: flex;
flex-direction: column;
justify-content: flex-end; /* now vertical: pushed to the bottom */
align-items: center; /* now horizontal: centered */
height: 160px;
background: #e5e7eb;
}
.col div { background: #2563eb; color: white; padding: 6px 12px; }
</style>
</head>
<body>
<div class="col"><div>One</div><div>Two</div></div>
</body>
</html>
如果你把它们搞混了,交换这两个属性即可,不必重写布局。
align-self 与 auto 外边距:只针对一个元素
在元素上设置 align-self,会为这个元素覆盖 align-items。flexbox 中没有 justify-self;要沿主轴移动单个元素,给它一个 auto 外边距,它会占据那一侧所有的剩余空间:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>align-self</title>
<style>
.bar { display: flex; align-items: flex-start; gap: 8px; height: 90px; background: #e5e7eb; }
.bar div { background: #2563eb; color: white; padding: 8px 12px; }
.bar .down { align-self: flex-end; background: #16a34a; }
.bar .last { margin-inline-start: auto; background: #f59e0b; }
</style>
</head>
<body>
<div class="bar">
<div>Top</div>
<div class="down">align-self: flex-end</div>
<div class="last">margin-inline-start: auto</div>
</div>
</body>
</html>
绿色元素独自落到底部,琥珀色元素被推到行的最末端,其他元素仍然留在起始处。
有多行时:align-content
align-items 在每一行内部对齐元素。当容器换行成多行时,align-content 负责分配行本身之间的空间,取值与 justify-content 相同。在 nowrap 容器中它不起作用,因为那唯一的一行总是占满容器的高度。flex-wrap 一页有演示。
常见错误
- 搞混了两条轴。
justify-content 是排列方向,align-items 是与之交叉的方向。在列布局中,垂直居中要用 justify-content: center。
- 没有剩余空间。 设置了
flex: 1 的元素会占据全部空间,justify-content 就没有可分配的空间了。容器是 inline-flex 且宽度刚好等于元素总宽时也是如此。
align-items 没有高度可用。 容器和内容一样高,就没有可对齐的空间。设置 height 或 min-height。
- 在 flexbox 中寻找
justify-self。 它在这里会被忽略。在元素上使用 margin-inline-start: auto(或 margin-left: auto)。
align-items: stretch 没有拉伸。 自身设置了 height 的元素会保持这个高度。去掉高度(如果需要最小高度,就用 min-height)。
- 把属性写在子元素上。 这两个属性都写在 flex 容器上。只有
align-self 写在元素上。