CSS定位与层叠上下文深入解析
position 五种取值的区别、z-index 为何失效、层叠上下文与包含块原理,前端布局基本功面试必考。
一句话概括
CSS 定位解决两件事:元素”在哪儿”(位置参考系) 和 “谁压在谁上面”(堆叠顺序)。前者靠 position,后者靠 z-index,但两者都绕不开一个核心概念——层叠上下文(Stacking Context)。
一句话记住:定位是前端最容易出 bug、也最见基本功的地方。你遇到过的”z-index 设了 99999 还是被挡”、”fixed 子元素不相对视口”、”sticky 死活不吸顶”,根源全在定位规则和层叠上下文上。
核心知识点
1. position 的五种取值
1
2
3
4
5
.box { position: static; } /* 默认,正常文档流,top/left/z-index 全无效 */
.box { position: relative; } /* 相对自身原位置偏移,原空间保留 */
.box { position: absolute; } /* 脱离文档流,相对最近的非 static 祖先 */
.box { position: fixed; } /* 脱离文档流,相对视口 */
.box { position: sticky; } /* 阈值内像 relative,超过阈值像 fixed */
- relative:视觉偏移,但原来的位置仍被占着,不影响别人
- absolute:完全脱档,不占空间,找”最近的定位祖先”当参照
- fixed:脱档,参照视口(有个大坑见第 6 节)
- sticky:relative 和 fixed 的混合体
2. 绝对定位的参照系:最近的”非 static 定位祖先”
1
2
3
<div class="parent"> <!-- 设了 position: relative -->
<div class="child"></div> <!-- absolute,相对 .parent 定位 -->
</div>
1
2
.parent { position: relative; width: 300px; height: 300px; }
.child { position: absolute; top: 0; right: 0; } /* 贴在 .parent 右上角 */
如果所有祖先都是 static,absolute 元素就找”初始包含块”(基本等于视口)当参照。常见坑:父元素只写了 transform 却没写 position,absolute 子元素并不会以它为参照——必须父元素是 relative/absolute/fixed 之一。
3. sticky 的三要素与失效排查
1
.sticky-nav { position: sticky; top: 0; } /* 滚到距顶 0px 时吸住 */
sticky 要生效,三个条件缺一不可:
- 设了
position: sticky - 设了阈值(
top/bottom/left/right至少一个) - 父容器能滚动、且没把溢出裁掉
1
2
/* ❌ 最常见失效原因:祖先 overflow: hidden 把滚动裁掉了 */
.ancestor { overflow: hidden; }
失效排查顺位:① 祖先 overflow: hidden/auto 裁剪了 → ② 父容器高度不够、没有滚动空间 → ③ 忘了设阈值 top/bottom。
4. 层叠上下文:z-index 只在”同一上下文”里比
层叠上下文像一个三维的”楼层”。元素创建了自己的层叠上下文后,它的所有子元素的 z-index 只能在这个楼层内部比,不能和外面跨层比较。
所以”z-index: 99999 还被挡”的真正原因:你的元素被关在某个父层叠上下文里,对手在另一个更高的楼层,你再大也只是在自家楼层里称王。
5. 什么会创建层叠上下文
| 触发条件 | 例子 |
|---|---|
| 根元素 | <html> 本身就是一个层叠上下文 |
定位 + z-index 非 auto | position: relative; z-index: 1 |
position: fixed / sticky | 直接创建(即使 z-index 为 auto) |
opacity < 1 | opacity: 0.9 |
transform / filter / perspective 非 none | transform: scale(1) |
will-change 指向上述属性 | will-change: transform |
isolation: isolate | 强制隔离创建 |
mix-blend-mode 非 normal | 混合模式 |
记忆:“定位带 z-index、transform、opacity、filter”是高频创建源,调试 z-index 时先把这些列出来。
6. 大坑:transform 改变 fixed/sticky 的包含块
1
2
3
.parent { transform: translate(0); } /* 即使只是 translate(0) */
.child { position: fixed; top: 0; }
/* ❌ .child 不再相对视口,而是相对 .parent! */
transform(以及 filter、perspective)会为后代元素创建新的包含块(containing block)。fixed 元素原本参照视口,一旦祖先有 transform,就改参照那个祖先了。这也是很多人”加了 transform 后弹窗定位全乱”的根源。
z-index 还有个例外:flex/grid 的直接子项,即使 position: static,z-index 也有效(用来调子项堆叠)。
其实你每天都在用
- 弹窗遮罩盖住全页:
position: fixed+ 高z-index蒙层 - 吸顶导航 / 表头:
position: sticky; top: 0 - 卡片右上角的关闭按钮:
absolute相对卡片定位 - 下拉菜单浮在内容上方:
absolute+z-index浮层 - modal 层级高于 header:用 CSS 变量统一分层(
--z-modal>--z-header) - 图片角标 / 优惠券标:
absolute贴边 - 滚动时目录高亮跟随:
sticky侧边栏,滚到哪亮到哪
常见误解(FAQ)
❌ 误区一:”z-index 设成 999999 就一定在最上面”
不一定。z-index 只在同一个层叠上下文里比大小。如果你的父元素自己在一个低楼层(比如父元素 opacity:0.9 或 transform 创建了上下文、且楼层号低),你再大的 z-index 也只在父楼层内部比,赢不了楼上的对手。排查要顺着祖先链找”谁创建了上下文、楼层号多少”。
❌ 误区二:”position: fixed 一定相对视口”
有个大坑:一旦 fixed 元素的祖先有 transform/filter/perspective,fixed 就改成相对那个祖先定位了(因为祖先成了新的包含块)。所以”加了 transform 后 fixed 跑偏”不是 bug,是规范行为。
❌ 误区三:”sticky 不生效是浏览器 bug”
绝大多数情况是自己写法问题:① 祖先 overflow: hidden 把滚动裁了(最常见);② 父容器没有足够高度、滚不起来;③ 忘了设 top/bottom 阈值。先查这三条。
❌ 误区四:”z-index 对所有元素都有效”
只对定位元素(relative/absolute/fixed/sticky)有效;普通 static 元素写 z-index 是无效的(flex/grid 直接子项是例外,可生效)。忘了加 position 是常见的”设了没反应”。
❌ 误区五:”层叠上下文只能靠 z-index 创建”
错。除了 position+z-index,transform、opacity<1、filter、will-change、isolation: isolate、position: fixed/sticky 等都会创建层叠上下文。很多”莫名其妙被挡”就是这些隐性上下文在作怪。
一句话总结
定位管”在哪儿”、z-index 管”谁在上”,但真正决定胜负的永远是层叠上下文——记住”z-index 只在同一楼层比大小、transform 会让 fixed 改认祖宗”,你就能从”拍 99999 玄学”变成”按规则排楼层”的靠谱前端。