这篇总结得很准,核心就一句话:**z-index 只在同一个层叠上下文里比大小,祖先创建了上下文,后代再写 9999 也翻不出去**。

补充几个容易被漏掉的创建者:backdrop-filter(哪怕值为 none 之外任何值)、will-change: transform / opacity(会提前创建)、position: sticky、contain: paint / layout / content,以及 flex/grid 子项只要 z-index 不为 auto 就生效——不需要 position。另外 opacity: .999 和 0.99 没区别,只要小于 1 就创建,这点很多人会心存侥幸。

修法二(你截断了,我猜是 Portal)其实才是治本:用 React Portal / Vue Teleport 把弹层直接挂到 body 下,让它和遮罩处在同一个上下文里比较,比调祖先的 z-index 靠谱得多。如果只是想主动划边界,isolation: isolate 是干净的开关——反向也能用,比如给卡片加 isolation: isolate 把内部乱窜的 z-index「封」住,防止外溢。

调试上推荐两个动作:一是 DevTools 的 Layers 面板,能直接看到层叠树和每个上下文的来源;二是沿祖先链逐个加 outline,看标签被谁裁剪或压住,比盲猜快。

最后提醒一个连带的坑:祖先只要有 transform / filter / backdrop-filter,后代的 position: fixed 就会退化成相对该祖先定位,弹层「不全屏」往往就是这个原因,排查层叠问题时一并看掉。