transform非none(如translateX(0)、scale(1)等)必触发新堆叠上下文,使内部z-index仅在该上下文中生效,无法与外部元素比较层级;这是规范强制行为,非bug,全浏览器一致执行。

因为 transform 会创建新的堆叠上下文(stacking context),让元素脱离原有层级体系——这不是 bug,是浏览器渲染机制的必然结果。
transform 怎么悄悄创建了新堆叠上下文?
只要一个元素的 transform 值不是 none(比如写了 transform: translateX(10px) 或 rotate(5deg)),它就自动成为独立堆叠上下文的根节点。此时它的子元素、兄弟元素之间的 z-index 比较,只在它内部生效,无法再和外部同级元素直接比大小。
- 常见误判:给弹窗加了
z-index: 9999,但父容器用了transform,结果弹窗被盖住——其实是弹窗的z-index只在那个 transform 容器内有效 - 连带影响:哪怕只是加了个
transform: translateZ(0)(常被用来触发硬件加速),也会创建新堆叠上下文 - 浏览器兼容性一致:Chrome、Firefox、Safari、Edge 全部按此规则执行,不是某家的“bug”
为什么 z-index 在 3D 场景里彻底失效?
当启用 transform-style: preserve-3d 后,子元素的 Z 轴位置由变换矩阵决定(如 translateZ(80px)),浏览器按投影后的实际 Z 坐标排序绘制,z-index 被完全忽略。
- 典型现象:
z-index: 999的元素仍被translateZ(-50px)的元素挡住,调高数值毫无作用 - 关键前提:
preserve-3d必须写在**直接父容器**上,且该父容器不能有opacity: 0.99、filter: blur(1px)等中断 3D 上下文的属性 - 注意:
translate3d(x, y, z)和translateZ(z)效果等价,但混用容易误判 Z 值来源
如何安全地控制层级而不掉坑?
优先靠结构和变换本身管理顺序,而非强行堆 z-index 数值。真要提层,得明确知道代价。
立即学习“前端免费学习笔记(深入)”;
- 对纯 2D 动画:动画开始前就设好足够高的
z-index,别让它参与transition——z-index不支持平滑过渡 - 对 3D 场景:统一用
translateZ()控制深度,差值建议 ≥ 30px,避免 1px/2px 这种视觉不可辨的微调 - 想“绝对置顶”:用
position: absolute+z-index单独抽离,但它会脱离preserve-3d流程,透视关系断裂,不能参与旋转/缩放联动
最易被忽略的一点:多个 transform 层级嵌套时,堆叠上下文是逐层建立的,每一层都可能截断外部 z-index 的影响力——调试时得一层层看 computed styles 里的 stacking context 标记,而不是只盯目标元素的 z-index 值。


















