display: flex 的作用
在父元素上写 display: flex,它的直接子元素就会排成一行,并排放置,拉伸到相同的高度。要把一排东西布局好,这一条声明就能取代浮动、inline-block 和大部分定位技巧。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>display flex</title>
<style>
body { font-family: system-ui, sans-serif; }
.box { background: #e5e7eb; padding: 8px; margin-bottom: 12px; }
.flex { display: flex; gap: 8px; }
.item { background: #2563eb; color: white; padding: 10px 14px; }
.tall { padding-block: 24px; }
</style>
</head>
<body>
<p>Without flex (normal block layout):</p>
<div class="box">
<div class="item">One</div>
<div class="item tall">Two</div>
<div class="item">Three</div>
</div>
<p>With display: flex:</p>
<div class="box flex">
<div class="item">One</div>
<div class="item tall">Two</div>
<div class="item">Three</div>
</div>
</body>
</html>
在第一个盒子中,每个 div 都占满一整行。在第二个盒子中,三个项目排在同一行,每个只和自己的内容一样宽,而 One 和 Three 被拉伸到和 Two 一样高。只改了父元素:display: flex 要写在容器上,永远不写在项目上。
Flexbox 演练场
选择不同的值,看看项目怎样移动。盒子下面那行代码就是你所看到效果的 CSS,再下面的列表是浏览器实际放置每个项目的位置,用 getBoundingClientRect() 测量得出。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Flexbox playground</title>
<style>
body { font-family: system-ui, sans-serif; font-size: 14px; }
.controls { display: flex; flex-wrap: wrap; gap: 8px 16px; margin-bottom: 10px; }
#box { display: flex; height: 170px; background: #e5e7eb; border: 2px solid #111827; }
.item { background: #2563eb; color: white; width: 60px; padding: 8px; box-sizing: border-box; }
.item:nth-child(2) { background: #16a34a; height: 80px; }
.item:nth-child(3) { background: #f59e0b; height: 50px; }
pre { white-space: pre-wrap; background: #f3f4f6; padding: 8px; }
</style>
</head>
<body>
<div class="controls">
<label>flex-direction <select data-p="flex-direction"><option>row</option><option>row-reverse</option><option>column</option><option>column-reverse</option></select></label>
<label>justify-content <select data-p="justify-content"><option>flex-start</option><option>center</option><option>flex-end</option><option>space-between</option><option>space-around</option><option>space-evenly</option></select></label>
<label>align-items <select data-p="align-items"><option>stretch</option><option>flex-start</option><option>center</option><option>flex-end</option><option>baseline</option></select></label>
<label>gap <select data-p="gap"><option>0px</option><option>8px</option><option>24px</option></select></label>
</div>
<div id="box">
<div class="item">1</div>
<div class="item">2</div>
<div class="item">3</div>
</div>
<pre id="out"></pre>
<script>
const box = document.getElementById('box');
const selects = document.querySelectorAll('select');
const update = () => {
const css = [];
selects.forEach((s) => { box.style.setProperty(s.dataset.p, s.value); css.push(s.dataset.p + ': ' + s.value + ';'); });
const b = box.getBoundingClientRect();
const places = [...box.children].map((el) => {
const r = el.getBoundingClientRect();
return el.textContent + ': x ' + Math.round(r.left - b.left - 2) + ', y ' + Math.round(r.top - b.top - 2) + ', ' + Math.round(r.width) + ' x ' + Math.round(r.height);
});
document.getElementById('out').textContent = '.box { display: flex; ' + css.join(' ') + ' }\n' + places.join('\n');
};
selects.forEach((s) => s.addEventListener('change', update));
update();
</script>
</body>
</html>
可以试试这些:
justify-content: space-between:项目 1 落在 x 0 处,项目 3 贴着另一侧的边缘。
align-items: center:三个项目保持各自的高度,中心对齐在一条线上。在默认的 stretch 下,项目 1 没有自己的高度,会填满容器的全部 170px。
flex-direction: column:现在 justify-content 让项目在垂直方向移动,align-items 在水平方向移动。这两个属性跟随的是轴,而不是屏幕。
主轴和交叉轴
每个 flex 容器都有两个轴。主轴是项目排列的方向,由 flex-direction 设置。交叉轴与主轴垂直。
flex-direction | 主轴 | 交叉轴 | justify-content 移动项目的方向 | align-items 移动项目的方向 |
|---|
row(默认值) | 水平,按阅读顺序 | 垂直 | 水平 | 垂直 |
row-reverse | 水平,方向相反 | 垂直 | 水平 | 垂直 |
column | 垂直,从上到下 | 水平 | 垂直 | 水平 |
column-reverse | 垂直,从下到上 | 水平 | 垂直 | 水平 |
在从右到左的页面中(dir="rtl"),row 从右边缘开始,因为主轴跟随书写方向。
容器属性
这些属性写在带 display: flex 的元素上:
| 属性 | 作用 | 默认值 |
|---|
flex-direction | 行还是列,以及朝哪个方向 | row |
flex-wrap | 空间不够时项目是否换到新的一行 | nowrap |
flex-flow | flex-direction 和 flex-wrap 的简写 | row nowrap |
justify-content | 沿主轴的位置和间距 | normal(表现为 flex-start) |
align-items | 项目沿交叉轴的位置 | normal(表现为 stretch) |
align-content | 项目换行时各行之间的间距 | normal |
gap | 项目之间的间距(row-gap、column-gap) | 0 |
justify-content 和 align-items 各有一个讲解所有取值的页面:justify-content 和 align-items。换行和方向见 flex-wrap 页面。
项目属性:flex-grow、flex-shrink、flex-basis
这些属性写在子元素上,决定它们如何分配容器的宽度:
flex-basis:分配剩余空间之前,项目的起始尺寸(auto 表示“用我的宽度或内容的宽度”)。
flex-grow:项目占据剩余空间的份数。0 表示它永远不会增长。
flex-shrink:空间不够时项目让出多少。0 表示它永远不会收缩。
flex 简写一次设置全部三个属性:
| 写法 | 含义 | 表现 |
|---|
flex: initial(默认值) | 0 1 auto | 内容尺寸,可以收缩,永远不增长 |
flex: 1 | 1 1 0% | 从零开始,平分空间 |
flex: auto | 1 1 auto | 内容尺寸再加上一份剩余空间 |
flex: none | 0 0 auto | 正好是内容或宽度的尺寸,既不增长也不收缩 |
flex: 2 | 2 1 0% | 份额是 flex: 1 项目的两倍 |
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>flex grow</title>
<style>
body { font-family: system-ui, sans-serif; }
.row { display: flex; gap: 8px; width: 480px; background: #e5e7eb; padding: 8px; margin-bottom: 8px; }
.row div { background: #2563eb; color: white; padding: 8px 0; text-align: center; }
.one { flex: 1; }
.row .two { flex: 2; background: #16a34a; }
.row .none { flex: none; width: 100px; background: #f59e0b; }
</style>
</head>
<body>
<div class="row"><div class="one">flex: 1</div><div class="one">flex: 1</div><div class="one">flex: 1</div></div>
<div class="row"><div class="one">flex: 1</div><div class="two">flex: 2</div><div class="one">flex: 1</div></div>
<div class="row"><div class="none">flex: none</div><div class="one">flex: 1</div></div>
<p id="out"></p>
<script>
document.getElementById('out').textContent = [...document.querySelectorAll('.row')].map((row) =>
[...row.children].map((c) => Math.round(c.getBoundingClientRect().width) + 'px').join(' + ')
).join(' | ');
</script>
</body>
</html>
每一行在内边距内宽 480px,两个 8px 的间距之后,留给项目的是 464px。打印出的宽度显示了空间是如何分配的:第一行每个约 155px;第二行是 116 + 232 + 116(flex: 2 的项目得到的份额是两边项目的两倍);第三行是 100 + 372,flex: none 的项目保持自己的宽度,另一个项目拿走其余所有空间。
用 flexbox 实现真实布局
开头放 logo、末尾放链接的页头是最常见的 flex 布局。在某个项目上写 margin-inline-start: auto,它会吃掉自己前面所有的剩余空间,把自己和后面的所有内容推到末尾:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Nav bar</title>
<style>
body { font-family: system-ui, sans-serif; margin: 0; }
nav { display: flex; align-items: center; gap: 16px; padding: 12px 16px; background: #111827; }
nav a { color: white; text-decoration: none; }
.logo { font-weight: 700; font-size: 20px; }
.push { margin-inline-start: auto; }
.cta { background: #2563eb; padding: 6px 12px; border-radius: 6px; }
</style>
</head>
<body>
<nav>
<a class="logo" href="#">Logo</a>
<a class="push" href="#">Docs</a>
<a href="#">Pricing</a>
<a class="cta" href="#">Sign up</a>
</nav>
</body>
</html>
align-items: center 让较大的 logo 文字和链接对齐,gap 设置链接之间的间距,不需要再为最后一个链接去掉外边距。
另一种日常用法是媒体行:一个固定尺寸的图标,加上占据其余空间并在自己的列中换行的文字。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Media object</title>
<style>
body { font-family: system-ui, sans-serif; }
.media { display: flex; align-items: flex-start; gap: 12px; max-width: 420px; padding: 12px; border: 1px solid #e5e7eb; border-radius: 8px; }
.avatar { flex: none; width: 48px; height: 48px; border-radius: 50%; background: #16a34a; }
.text { flex: 1; min-width: 0; margin: 0; }
</style>
</head>
<body>
<div class="media">
<div class="avatar"></div>
<p class="text">This text wraps next to the circle. However long it gets, the circle keeps its 48px size because it has flex: none.</p>
</div>
</body>
</html>
flex: none 是关键的保证。flex 项目默认会收缩,所以如果去掉圆形上的它,同时去掉文字上的 flex: 1,段落就会把圆形挤到约 23px 宽:变成椭圆。要让一个东西在盒子里居中,flexbox 只需要两行(justify-content: center; align-items: center);div 居中页面把它和其他方法做了对比。
order 与无障碍
order 属性和 -reverse 方向改变的是项目显示的位置,而不是它们在 HTML 中的位置。屏幕阅读器和 Tab 键仍然按照 HTML 中的顺序,所以键盘用户可能会从屏幕上的第一个链接直接跳到最后一个。它们只用于装饰;当顺序有实际含义时,请修改 HTML。
flexbox 还是 grid
Flexbox 一次只在一个方向上工作:一行中的项目根据内容决定尺寸,换行后的第二行不会和第一行对齐。Grid 在两个方向上工作:你定义好列和行,项目就会对齐到网格中。导航栏、工具栏或表单行用 flexbox;图片墙或带侧边栏的页面用 grid。
常见错误
- 把
display: flex 写在项目上,而不是父元素上。 容器设置 display: flex,它的子元素才是项目。
- 没有高度却期望
align-items 让项目垂直移动。 如果容器只和最高的项目一样高,就没有可以移动的空间。给容器设置高度;否则它其实在正常工作,只是没什么可做的。
- 文字溢出而不是收缩。 flex 项目带有
min-width: auto。给需要收缩的项目加上 min-width: 0。
- 用了
flex: 1,却奇怪宽度为什么不一样。 带 flex: 1 的项目会平分空间,但内容放不下的项目(一个很长的 URL、一张图片)不会收缩到比内容更窄。min-width: 0 同样能解决这个问题。
- 在项目之间用外边距。 在容器上用
gap;它只作用于项目之间。
- 不必要的嵌套 flex。 只有直接子元素才是 flex 项目。孙元素遵循正常布局,除非它们自己的父元素也是
display: flex。