「让一个块在页面里垂直居中」大概是 CSS 史上被问过最多的问题之一。从 vertical-align 的玄学到 50% + 负 margin 的黑科技,一代前端人踩过无数坑。而在现代 CSS 里,这件事其实只需要三行代码。
本文介绍几种让内容在整个浏览器可视区域垂直居中的方法,从最推荐的 Flexbox / Grid 写法,到移动端 100vh 的经典坑,一次讲清楚。
先搞清楚:什么叫"整个浏览器显示区域"
要让内容相对视口(viewport)垂直居中,首先要让容器本身占满整个视口高度。常见的错误是只写了 height: 100%——但百分比高度需要父元素一路都有确定高度,html、body 默认都是auto,所以经常"不生效"。
正确姿势是用 vh 单位或 dvh 单位直接声明视口高度:
.page {
min-height: 100vh; /* 至少占满一屏,内容超出时自动撑开 */
}
min-height: 100vh 而不是 height: 100vh。后者在内容超过一屏时会溢出,前者更健壮。方法一:Flexbox(最推荐)
容器占满视口后,交给 Flexbox 完成居中,水平垂直各一行:
.page {
display: flex;
flex-direction: column; /* 可选:让子项纵向排列 */
justify-content: center; /* 主轴居中(配合 column 即垂直居中) */
align-items: center; /* 交叉轴居中(水平居中) */
min-height: 100vh;
}
对应的 HTML 结构非常简单:
<body class="page">
<div class="hero">
<h1>你好,世界</h1>
<p>这段内容永远在屏幕正中间</p>
</div>
</body>
如果不想改变子元素的排列方式,还有一个更简洁的变体——flex + margin auto:
.page {
display: flex;
min-height: 100vh;
}
.hero {
margin: auto; /* flex 容器里 margin:auto 会自动吸收剩余空间 */
}
方法二:CSS Grid(最短写法)
Grid 是这个问题下的"答案之王"——一个 place-items 属性搞定水平 + 垂直居中:
.page {
display: grid;
place-items: center; /* 等价于 align-items:center + justify-items:center */
min-height: 100vh;
}
两行核心代码,没有嵌套限制,子元素数量无所谓,这就是现代 CSS 的优雅之处。
方法三:absolute + transform(老项目兼容写法)
在 Flexbox 还没普及的年代,这是最通用的方案,如今在一些需要兼容超老浏览器、或者要求元素脱离文档流的场景(弹窗、loading 遮罩)仍然常用:
.page {
position: relative;
min-height: 100vh;
}
.hero {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%); /* 往回挪自身宽高的一半 */
}
注意 translate(-50%, -50%) 的百分比是相对元素自身尺寸计算的,所以不管内容多高多宽都能精确居中——这正是它比"负 margin"写法优越的地方。
方法四:table-cell(考古方案)
早年还有借助表格布局的写法,原理是 vertical-align: middle 只在表格单元格里对块级元素生效:
.page {
display: table;
width: 100%;
min-height: 100vh;
}
.page-inner {
display: table-cell;
vertical-align: middle;
text-align: center;
}
移动端的坑:100vh 并不等于"可视区域"
在手机上,浏览器地址栏会随滚动收起/展开,导致 100vh 算出来的是地址栏收起后的最大高度——内容底部会被地址栏遮住一截。这是移动端全屏布局的经典 bug。
现代浏览器提供了三个新单位来解决:
| 单位 | 含义 | 适用场景 |
|---|---|---|
100svh | 最小视口高度(地址栏展开时) | 保证首屏内容不被遮挡 |
100lvh | 最大视口高度(地址栏收起时) | 背景图、全屏装饰 |
100dvh | 动态视口高度(随地址栏实时变化) | 需要"正好一屏"的场景 |
兼容性兜底写法:
.page {
min-height: 100vh; /* 老浏览器兜底 */
min-height: 100dvh; /* 支持新单位的浏览器自动覆盖上一行 */
}
四种方法对比
| 方法 | 代码量 | 内容超一屏时 | 是否脱离文档流 | 推荐度 |
|---|---|---|---|---|
| Flexbox | 3 行 | min-height 下正常撑开 | 否 | ★★★★★ |
| Grid | 2 行 | min-height 下正常撑开 | 否 | ★★★★★ |
| absolute + transform | 5 行 | 会溢出,需自己处理滚动 | 是 | ★★★(弹窗/遮罩场景) |
| table-cell | 4 行 + 额外层级 | min-height 不生效 | 否 | ★(仅考古) |
常见问题排查
写了 display:flex 还是没居中?
九成原因是容器高度没有真正撑满。检查:
- 居中容器上是否有
min-height: 100vh(或100dvh) - 是不是把居中等属性写在了子元素上——
justify-content/align-items只对 flex 容器的直接子元素生效 - 子元素是否设置了
height: 100%之类的样式,反而顶掉了居中效果
body 上直接写 flex 会不会影响其他布局?
会。flex 容器里的子元素默认收缩排列,如果页面还有页头页脚,更稳妥的做法是只给"需要居中的那一屏"套 .page 容器,而不是全局改 body。
总结
现代 CSS 里,全屏垂直居中的标准答案就是:
.page {
display: grid;
place-items: center;
min-height: 100vh;
min-height: 100dvh; /* 移动端修正 */
}
CSS 垂直居中从"世纪难题"变成"一行代码",前后端经历了十五年。
—— 别忘了把老教程里的黑科技从收藏夹里删掉。