彻底搞懂 position 定位与层叠上下文
上周帮同事看一个 bug:弹窗设置了 z-index: 9999,愣是被一个 z-index: 10 的侧边栏盖住。他满脸问号,我一看代码就明白了——弹窗的父元素是个 transform 元素,层叠上下文把它框死了。
这个坑我当年也踩过,今天把 position 和层叠上下文一次性捋清楚。
position 的五个值
先过一遍基础,重点是容易混淆的几个:
static:默认值,正常文档流,top/left 等偏移无效relative:相对自己原来的位置偏移,不脱离文档流,原位置空间保留absolute:相对最近的非 static 祖先定位,脱离文档流fixed:相对视口定位,脱离文档流sticky:relative 和 fixed 的混合体,滚动到阈值时"粘住"
最常见的错误:给 absolute 元素定位,结果参照物不对。
<div class="parent">
<div class="child">我往哪飞?</div>
</div>
<style>
.parent {
/* 忘了写这行,child 就相对 body 定位了 */
position: relative;
width: 300px;
height: 300px;
}
.child {
position: absolute;
top: 20px;
right: 20px;
}
</style>
记住一句话:absolute 找爸爸,爸爸必须非 static。一般做法就是给父元素加 position: relative,不产生偏移但能当参照物。
sticky 为什么有时候不粘
sticky 很好用,做吸顶导航一行搞定:
.nav {
position: sticky;
top: 0;
}
但它有两个经典失效场景:
- 父元素有
overflow: hidden / auto / scroll,sticky 的滚动参照被改了 - 父元素高度不够,sticky 的活动范围就是父元素的内容区,父元素和子元素一样高时根本没地方"粘"
我排查 sticky 失效的第一步永远是:往上翻父级,查 overflow 和高度。
层叠上下文:z-index 的真正规则
回到开头的 bug。z-index 不是全局比大小,它只在同一个层叠上下文里比较。
默认情况下,根元素(html)是唯一的层叠上下文,大家按 z-index 排队。但以下属性会创建新的层叠上下文:
position: relative/absolute/fixed且 z-index 非 autoposition: stickyopacity < 1transform非 nonefilter非 noneflex/grid 容器的子项且 z-index 非 autowill-change、isolation: isolate、mix-blend-mode等
一旦创建了新的层叠上下文,这个元素连同它的所有后代就是一个整体,对外只用自己的 z-index 参与比较。
<div class="sidebar" style="position: relative; z-index: 10;">侧边栏</div>
<div class="wrapper" style="transform: translateX(0);">
<!-- 弹窗 z-index 再大也没用,wrapper 整体对外没有 z-index(按 0 算) -->
<div class="modal" style="position: fixed; z-index: 9999;">弹窗</div>
</div>
上面的结构里,wrapper 因为 transform 创建了层叠上下文,modal 的 9999 只在 wrapper 内部有效。对外比较的是 wrapper(z-index 按 auto 算)和 sidebar(z-index: 10),弹窗必输。
修复思路
知道原理后,解法就清晰了:
- 把弹窗挪到 body 直属层级,别让它被困在某个层叠上下文里——这也是为什么 Vue/React 组件库里 Modal 都用 Teleport/Portal 挂到 body 下
- 如果挪不动,就调整两个层叠上下文的"家长",让弹窗所在上下文的层级高过遮挡物
- 万不得已才动 z-index 数字本身,而且别搞 9999 vs 99999 的军备竞赛
<!-- 正确姿势:弹窗直接挂 body -->
<body>
<div class="sidebar" style="position: relative; z-index: 10;">侧边栏</div>
<div class="modal" style="position: fixed; z-index: 100;">弹窗,稳赢</div>
</body>
小结
- absolute 找最近的非 static 祖先定位;sticky 失效先查父级 overflow
- z-index 只在同一层叠上下文内比大小
- transform、opacity < 1、filter 等都会创建层叠上下文,把后代的 z-index "框死"
- 弹窗类组件直接挂 body,从结构上避开这类问题
下次再遇到"z-index 失灵",别急着加 9,先打开 DevTools 看看元素被哪个层叠上下文困住了。
看到这里了?如果这篇文章帮到过你
请我喝瓶可乐就好 🥤

评论0 条
还没有评论,来抢沙发~