z-index 被 transform 影响并非失效,而是 transform 非 none 值会强制创建层叠上下文,使子元素 z-index 仅在内部生效,无法与外部同级元素比较;opacity、filter、will-change 等属性同理。

z-index 被 transform 影响,不是它“失效”了,而是你正在跟一个新创建的层叠上下文(stacking context)打交道——这个上下文把子元素的 z-index 隔离在内部,无法和外部同级元素比高低。
transform 非 none 值会强制创建层叠上下文
只要一个元素的 transform 值不是 none(比如 translateX(0)、scale(1)、translateZ(0)),它就立刻成为新的层叠上下文根节点。这不是 bug,是 CSS 规范明确要求的行为,所有现代浏览器(Chrome/Firefox/Safari/Edge)都一致执行。
- 该元素自身及其所有后代,z-index 只在它内部起作用
- 哪怕子元素设了
z-index: 999999,也出不了这个“结界” - 它的兄弟元素、父容器外的 fixed 导航栏、body 下的弹窗,全都不在一个比较体系里
- DevTools 的 «Layers» 面板会标出 “Stacking Context”,可直接验证
常见误判场景:你以为在调 z-index,其实是在调“牢房编号”
典型现象包括:
- 弹窗组件明明写了
z-index: 9999,却被旁边一个加了transform: translateY(2px)的卡片盖住 - Grid 容器设了
transform,里面的grid-item再怎么调z-index也盖不过 body 下的 tooltip - 移动端
position: fixed导航栏被轮播图 wrapper 盖住,而 wrapper 只加了will-change: transform或translateZ(0) -
body或html上写了transform: translateZ(0)→ 整个页面变成单一层叠上下文,“全局最高”的弹窗也只能在它内部排第一
为什么 opacity/filter/will-change 也会导致同样问题
它们和 transform 一样,都是触发层叠上下文的“高危属性”。只要任意父级(哪怕隔了三四层)满足以下任一条件,就会形成隔离:
-
opacity小于 1(如opacity: 0.99) -
filter不为none(如blur(1px)) -
will-change: transform(即使没真动,只是“预告”) -
backdrop-filter、clip-path、isolation: isolate
这些属性常被用作性能优化手段,但代价是悄无声息地切分了层叠关系——调试时最容易忽略的就是这些“看不见的父级”。
真正要改的不是 z-index 数值,而是上下文归属
强行堆高 z-index 没用;关键得让目标元素和它想盖住的对象处于可比较的层级中:
- 优先移除不必要的
transform(尤其是translateZ(0)这类“伪加速”) - 若必须保留动画,把
z-index加在触发transform的那个元素自己身上,并确保它已定位(position: relative等)且数值大于同级竞争者 - 对关键浮层(Modal/Tooltip),用
React Portal或Vue Teleport直接挂到body下,绕过所有布局容器的上下文污染 - 结构可控时,用 DOM 顺序代替 z-index:比如把
tbodyprepend到thead前,靠文档流自然压住表头
最麻烦的从来不是写多大的 z-index,而是你根本没意识到那个加了 transform 的 div 已经成了独立王国——它的子元素再怎么喊 z-index: 999999,也出不了门。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











