先记住这个答案
写法是父元素设 position: relative,子元素设 position: absolute、inset: 0、固定的 width 和 height,再加 margin: auto。原理是绝对定位元素在水平方向必须满足 left + margin-left + 宽度 + margin-right + right = 包含块宽度,垂直方向同理。偏移量都是 0、宽高又是固定值时,等式里只剩两个 auto 外边距是未知数,浏览器就把剩余空间平分给它们,元素自然在两个方向上都居中。
- 四个方向设
0,再给宽高和margin: auto - 包含块剩余空间被两个
auto外边距平分 - 宽高必须是确定值,
auto会直接撑满 - 普通流里
margin: auto只能水平居中
先看写法,再算一遍等式
假设父元素宽 400px、高 300px,子元素宽 200px、高 100px,下面把浏览器的计算过程摆出来。
.parent {
position: relative; /* 让 parent 成为子元素的包含块 */
width: 400px;
height: 300px;
}
.child {
position: absolute;
inset: 0; /* 等同于 top、right、bottom、left 都是 0 */
width: 200px;
height: 100px;
margin: auto; /* 水平剩 200px,每边 100px;垂直剩 200px,每边 100px */
}水平方向:0 + margin-left + 200 + margin-right + 0 = 400,两个外边距加起来要等于 200px,规范规定两个都是 auto 时取相等的值,所以各 100px。垂直方向算法一样,剩下的 200px 上下各分 100px。子元素的位置正好在正中间。
这里的「宽度」严格说还包含左右 padding 和 border。子元素如果有 padding: 20px 又是默认的 content-box,实际占的宽度是 240px,外边距会相应变小,但依然是两边平分,居中不受影响。
inset: 0和四个0是一回事inset是top、right、bottom、left的简写,inset: 0等于四个都写0。老项目里会看到四行分开写,效果一样。也不一定非得是0,写成inset: 20px就是在上下左右各留出20px后的区域里居中。
为什么普通流里 margin: auto 做不到垂直居中
普通块级元素写 margin: 0 auto 能水平居中,但 margin-top: auto 和 margin-bottom: auto 在普通流里会被当成 0。原因是普通流的块级元素在垂直方向没有「要凑满父元素高度」的等式,高度是由内容往下排出来的,没有剩余空间可分。
绝对定位把元素从流里拿出来,同时给了垂直方向一个和水平方向对称的等式:top + margin-top + 高度 + margin-bottom + bottom = 包含块高度。有了这个等式,上下 auto 外边距才有东西可分。所以这个写法的关键不是 margin: auto 本身,而是绝对定位加上四个方向都给了偏移量。
- Flex 和 Grid 里的
margin: auto - Flex 子项和 Grid 子项的
auto外边距也会吃掉剩余空间,所以父元素display: flex后,子元素写margin: auto同样能两个方向居中,而且不需要固定宽高。道理相通,都是「剩余空间分给auto外边距」。
容易答错的地方
- 忘了给宽高
- 宽高是
auto时,浏览器会优先让width去填满剩余空间,auto外边距直接算成0,结果元素铺满整个父元素,看起来像居中失效。宽高要么是具体值,要么是百分比,也可以用width: fit-content让它按内容收缩,同样能居中。 - 父元素没设定位
position: absolute相对的是最近的非static定位祖先。父元素没写position: relative,就会一路往上找,找不到就用初始包含块,元素会跑到页面中间去。- 元素比父元素还大
- 剩余空间是负数时,水平方向在从左到右的书写方向下,
margin-left取0,元素贴着左边往右溢出,不再对称。这时候应该先检查尺寸,而不是期待它向两边均匀溢出。
面试官还会怎么问?
宽高不固定时怎么居中?
最常用的是 top: 50%; left: 50%; transform: translate(-50%, -50%),百分比的 translate 相对元素自身尺寸计算,不用知道宽高。更现代的写法是父元素 display: grid; place-items: center,一行就够。绝对定位方案也可以配合 width: fit-content; height: fit-content 使用。
transform 方案有什么副作用?
transform 会让元素建立新的层叠上下文,也会成为它内部 position: fixed 元素的包含块,里面的弹窗可能不再相对视口定位。另外 translate 算出半像素时,文字边缘可能发虚。margin: auto 方案没有这些问题。
做全屏弹窗遮罩时为什么常用这个写法?
遮罩层 position: fixed; inset: 0 铺满视口,弹窗本体再用 position: absolute; inset: 0; margin: auto 配合固定宽高居中,不依赖 transform,视口尺寸变化时也会自动保持居中。
参考资料
示例用于理解所注明的运行环境与边界;延伸学习可结合原文中的更多案例。