移动端z-index失效主因是目标元素被祖先创建的独立层叠上下文隔离;需检查position是否非static,并排查opacity

移动端 z-index 失效,几乎从不因为数值写小了,而是目标元素被某个祖先“关进了一个独立层叠上下文里”——它再高,也出不去那个盒子。
检查目标元素是否已定位
z-index 只对 position 为 relative、absolute、fixed 或 sticky 的元素生效。默认的 static 下,z-index 完全被浏览器忽略,连 warning 都不报。
- 用 Chrome DevTools 查看该元素的 «Computed» 面板,确认
position不是static - JS 动态插入浮层(如 toast、picker)时,常漏掉
position声明,必须显式设置position: fixed或position: absolute -
display: flex或grid的子项即使没设position,也能用z-index控制内部顺序,但依然受父级层叠上下文隔离——这点容易误判
定位哪个父级在创建“结界”
只要某个祖先元素满足以下任一条件,它就创建了新的层叠上下文(stacking context),子元素的 z-index 就只在它内部起作用:
-
opacity小于 1(哪怕opacity: 0.999) -
transform不为none(包括transform: translateZ(0)、scale(1)、rotate(0.0001deg)) -
filter不为none(哪怕filter: blur(0.1px)) -
will-change设为transform、opacity等值 isolation: isolate-
position: fixed或sticky且z-index不是auto
用 Chrome DevTools 的 Elements 面板逐级点击父节点,在右侧面板 «Layout» 标签页下看 «Stacking context» 是否突然变成 Yes;重点盯那些“没写 z-index 却显示 Yes”的父元素。
立即学习“前端免费学习笔记(深入)”;
修复必须同时处理定位与上下文
只改 z-index 数值没用;必须让目标元素既“能定位”,又“不被关住”。
- 临时验证法:注释掉父级的
transform、opacity或filter,立刻恢复覆盖——这是最快确认手段 - 若父容器必须保留
translateZ(0)(比如做动画),就把需要浮出的元素提出来:document.body.append(el),再用 JS 计算top/left定位 - 避免给
fixed元素的父级加transform、perspective、opacity ——iOS Safari 会直接忽略 <code>fixed语义 - React 中用
createPortal挂载到document.body是最稳妥方案,但要注意滚动偏移:监听window.scrollY并更新top值
iOS Safari 中 fixed 元素降级问题
只要父容器有 transform、opacity 或 <code>filter,iOS Safari 就可能把 position: fixed 退化为 absolute 行为,失去视口定位能力。
- 现象:
z-index: 9999的 Modal 按钮始终被 header 盖住;DevTools 里z-index显示为(not applicable)或灰显 - 别信
will-change: transform更安全——它和translateZ(0)触发机制一致,同样建“结界” - Bootstrap / Vue / React 组件常自带
transform或opacity,导致嵌套浮层失效;不能只调子组件样式,得查整个 DOM 路径
真正难的不是堆数字,而是看清哪一层“结界”把你卡住了——尤其是 transform: translateZ(0) 这种看着无害、却在 iOS Safari 上静默立墙的写法。


















