「让一个块在页面里垂直居中」大概是 CSS 史上被问过最多的问题之一。从 vertical-align 的玄学到 50% + 负 margin 的黑科技,一代前端人踩过无数坑。而在现代 CSS 里,这件事其实只需要三行代码。

本文介绍几种让内容在整个浏览器可视区域垂直居中的方法,从最推荐的 Flexbox / Grid 写法,到移动端 100vh 的经典坑,一次讲清楚。

先搞清楚:什么叫"整个浏览器显示区域"

要让内容相对视口(viewport)垂直居中,首先要让容器本身占满整个视口高度。常见的错误是只写了 height: 100%——但百分比高度需要父元素一路都有确定高度,html、body 默认都是auto,所以经常"不生效"。

正确姿势是用 vh 单位或 dvh 单位直接声明视口高度:

css base.css
.page {
  min-height: 100vh;   /* 至少占满一屏,内容超出时自动撑开 */
}
💡 小技巧:优先用 min-height: 100vh 而不是 height: 100vh。后者在内容超过一屏时会溢出,前者更健壮。

方法一:Flexbox(最推荐)

容器占满视口后,交给 Flexbox 完成居中,水平垂直各一行:

css center-flex.css
.page {
  display: flex;
  flex-direction: column;      /* 可选:让子项纵向排列 */
  justify-content: center;     /* 主轴居中(配合 column 即垂直居中) */
  align-items: center;         /* 交叉轴居中(水平居中) */
  min-height: 100vh;
}

对应的 HTML 结构非常简单:

html index.html
<body class="page">
  <div class="hero">
    <h1>你好,世界</h1>
    <p>这段内容永远在屏幕正中间</p>
  </div>
</body>

如果不想改变子元素的排列方式,还有一个更简洁的变体——flex + margin auto:

css center-margin.css
.page {
  display: flex;
  min-height: 100vh;
}
.hero {
  margin: auto;   /* flex 容器里 margin:auto 会自动吸收剩余空间 */
}

方法二:CSS Grid(最短写法)

Grid 是这个问题下的"答案之王"——一个 place-items 属性搞定水平 + 垂直居中:

css center-grid.css
.page {
  display: grid;
  place-items: center;   /* 等价于 align-items:center + justify-items:center */
  min-height: 100vh;
}

两行核心代码,没有嵌套限制,子元素数量无所谓,这就是现代 CSS 的优雅之处。

✅
选型建议
单个元素居中用 Grid + place-items;多个元素需要排列控制(如纵向堆叠的登录卡片)用 Flexbox。

方法三:absolute + transform(老项目兼容写法)

在 Flexbox 还没普及的年代,这是最通用的方案,如今在一些需要兼容超老浏览器、或者要求元素脱离文档流的场景(弹窗、loading 遮罩)仍然常用:

css center-abs.css
.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 只在表格单元格里对块级元素生效:

css center-table.css
.page {
  display: table;
  width: 100%;
  min-height: 100vh;
}
.page-inner {
  display: table-cell;
  vertical-align: middle;
  text-align: center;
}
⚠️
不推荐
table-cell 方案需要额外的包裹层,而且 min-height 对 display:table 不生效,实际用起来坑很多。列出仅为让大家看懂老代码。

移动端的坑:100vh 并不等于"可视区域"

在手机上,浏览器地址栏会随滚动收起/展开,导致 100vh 算出来的是地址栏收起后的最大高度——内容底部会被地址栏遮住一截。这是移动端全屏布局的经典 bug。

现代浏览器提供了三个新单位来解决:

单位含义适用场景
100svh最小视口高度(地址栏展开时)保证首屏内容不被遮挡
100lvh最大视口高度(地址栏收起时)背景图、全屏装饰
100dvh动态视口高度(随地址栏实时变化)需要"正好一屏"的场景

兼容性兜底写法:

css viewport.css
.page {
  min-height: 100vh;    /* 老浏览器兜底 */
  min-height: 100dvh;   /* 支持新单位的浏览器自动覆盖上一行 */
}

四种方法对比

方法代码量内容超一屏时是否脱离文档流推荐度
Flexbox3 行min-height 下正常撑开否★★★★★
Grid2 行min-height 下正常撑开否★★★★★
absolute + transform5 行会溢出,需自己处理滚动是★★★(弹窗/遮罩场景)
table-cell4 行 + 额外层级min-height 不生效否★(仅考古)

常见问题排查

写了 display:flex 还是没居中?

九成原因是容器高度没有真正撑满。检查:

  1. 居中容器上是否有 min-height: 100vh(或 100dvh)
  2. 是不是把居中等属性写在了子元素上——justify-content / align-items 只对 flex 容器的直接子元素生效
  3. 子元素是否设置了 height: 100% 之类的样式,反而顶掉了居中效果

body 上直接写 flex 会不会影响其他布局?

会。flex 容器里的子元素默认收缩排列,如果页面还有页头页脚,更稳妥的做法是只给"需要居中的那一屏"套 .page 容器,而不是全局改 body。

总结

现代 CSS 里,全屏垂直居中的标准答案就是:

css 最终方案
.page {
  display: grid;
  place-items: center;
  min-height: 100vh;
  min-height: 100dvh;   /* 移动端修正 */
}
CSS 垂直居中从"世纪难题"变成"一行代码",前后端经历了十五年。
—— 别忘了把老教程里的黑科技从收藏夹里删掉。