transform非none会创建新层叠上下文,使子元素z-index仅在内部生效;需检查computed样式中stacking context是否为Yes,并排查祖先节点的transform、opacity等触发属性。

transform 导致 z-index “失效”,不是 z-index 没起作用,而是它被锁进了一个新创建的层叠上下文(stacking context)里——子元素再怎么设 z-index: 9999,也出不了那个“盒子”。
为什么 transform 会让 z-index 看似失效
只要元素的 transform 值不是 none(哪怕只是 transform: translateZ(0)、scale(1) 或 rotate(0.0001deg)),它就自动成为新的层叠上下文根节点。此时:
- 该元素自身在文档层叠顺序中获得一个独立“排位”,和兄弟元素比高低
- 它的所有子元素的
z-index只在这个上下文内部生效,无法和外部元素(比如position: fixed导航栏、body下的弹窗)直接比较 - 常见现象:带缩放动画的卡片盖不住 sticky 头部;Modal 内的按钮被轮播图遮住;iOS Safari 下
position: fixed直接降级为absolute
快速定位哪个 transform 在作祟
打开 Chrome DevTools,选中目标元素,在右侧面板 «Computed» 中搜索 stacking context:
- 如果显示 Yes,说明它或某个祖先已创建层叠上下文
- 切换到 «Elements» 面板,逐级点击父节点,在 «Layout» 标签下看 «Stacking context» 是否突然变成 Yes
- 重点检查这些属性是否被激活:
transform、opacity(filter(非none)、will-change(含transform或opacity) - 临时注释掉疑似父容器的
transform,观察遮挡是否立刻恢复正常——这是最准的验证方式
真正有效的修复方式,不是调高 z-index
别再给子元素狂加 z-index: 99999,它破不了父级的“结界”。关键动作是让目标元素脱离或跨越上下文边界:
立即学习“前端免费学习笔记(深入)”;
- 给触发
transform的父容器本身显式设置position: relative+z-index(值要大于同级竞争者,比如z-index: 1000) - 若该容器必须动(如 hover 缩放),且需覆盖外部固定元素(如导航栏),就把需要浮出的内容提出来:
document.body.append(el),再用 JS 补top/left定位 - 对关键浮层(Tooltip、Modal、Toast),优先用框架机制脱离 DOM 原位置:React 的
createPortal、Vue 的Teleport、或手动挂载到body - 避免组合陷阱:不要在同一个父容器上同时写
transform: translateZ(0)和opacity: 0.99——双重触发,上下文更难解
移动端和 iOS Safari 要特别当心
iOS Safari 对 transform 创建层叠上下文极其敏感,甚至比 Chrome 更早、更彻底:
-
transform: translateZ(0)在父容器上一出现,子元素的z-index就基本失效,position: fixed还可能被降级 - 检查媒体查询中是否只在
@media (max-width: 768px)里加了transform,却忘了同步处理层级 - 动态插入的弹窗(如 Vue
v-if或 ReactuseState控制的 Modal)必须显式设position: fixed和z-index,不能依赖父组件样式继承 - 用
getComputedStyle(el).getPropertyValue('z-index')在控制台验证断点生效后的实际值,注意auto和0的语义差异:z-index: 0会强制创建新上下文
最难的从来不是算清该写多少,而是你根本没意识到那个加了 transform: scale(1) 的 div 已经成了“独立王国”——它的子元素再怎么喊 z-index: 999999,也出不了门。


















