先记住这个答案
层叠上下文是一个独立的图层组。组内元素按 z-index 和层叠顺序排好,整个组再作为一个整体,用创建它的那个元素的层级去和外面比较。常见的创建条件有:根元素 html;position 为 relative 或 absolute 且 z-index 不是 auto;position: fixed 或 sticky;opacity 小于 1;transform、filter 不为 none;Flex 或 Grid 子项设了 z-index;isolation: isolate。它对 z-index 的影响是:子元素的 z-index 被限制在父级上下文里,父元素排在别人下面,子元素写 9999 也一样在下面。
z-index只和同一个层叠上下文里的元素比- 父元素的层级决定了子元素能到的最高位置
- 透明度与变换都可建层叠上下文
- Flex 和 Grid 子项设
z-index也会创建
弹窗写了 z-index: 9999 还被盖住
这是项目里最常见的场景。弹窗组件放在某个区块里,区块有淡入动画,用了 opacity。页面上的吸顶导航 z-index: 10,结果弹窗打开时被导航盖住了一截。
opacity 把弹窗圈住了CSS.header {
position: sticky;
top: 0;
z-index: 10;
}
.section {
opacity: 0.99; /* 动画残留,小于 1 就创建了层叠上下文 */
}
.section .modal {
position: fixed;
z-index: 9999; /* 只在 .section 内部有效 */
}
/* 结果:.section 整体按层级 0 和 .header 的 10 比较,
.modal 虽然是 9999,也只能跟着 .section 排在 .header 下面 */.modal 的 9999 只和 .section 里的其他元素比。到了外层,参与比较的是 .section 这个整体,它相当于 z-index: 0,比导航的 10 小,所以整个 .section 连同里面的弹窗都在导航下面。
- 怎么修
- 最稳的办法是把弹窗挂到
body下面,React 用createPortal,Vue 用Teleport,这样弹窗就不受中间任何祖先的上下文影响。其次是去掉祖先上不必要的opacity、transform,或者给.section本身设一个比导航大的z-index,但后者会把整个区块都抬上去,往往不是想要的效果。
层叠上下文内部怎么排
在同一个上下文里,元素从下到上大致分成七层:上下文根元素的背景和边框,z-index 为负的子上下文,普通块级元素,浮动元素,行内元素,z-index 为 auto 或 0 的定位元素,z-index 为正的子上下文。同一层里后出现的盖住先出现的。
- 为什么负
z-index的子元素还在父元素背景上面 - 子元素设了
z-index: -1,很多人以为它会跑到父元素后面去。如果父元素本身创建了层叠上下文,父元素的背景就是这个上下文的最底层,负z-index的子元素只能排在背景上面、普通内容下面。只有父元素不创建上下文时,子元素才可能钻到父元素背景后面,一直沉到更外层的上下文里。 opacity和transform为什么也会创建- 它们需要把元素和所有子元素当成一整块来处理。
opacity: 0.5要先把整块画好,再整体变半透明;如果子元素能和外面的元素交错排序,这一步就没法做。所以浏览器规定这些属性必须形成独立的上下文。filter、mask、mix-blend-mode也是同样的原因。
容易答错的地方
- 以为
z-index越大就一定在上面 z-index只在同一个层叠上下文里比较大小。两个元素分属不同上下文时,比的是它们各自往上找到的、处在同一上下文里的祖先。所以排查遮挡问题时先看祖先,不要先去改数字。- 以为只有定位元素才会创建层叠上下文
opacity、transform、filter、will-change、contain: paint这些和定位无关的属性都会创建。尤其是动画库给元素加的transform和opacity,经常在不知不觉中制造出层叠上下文。- 以为
position: relative就会创建层叠上下文 - 单独的
position: relative或absolute不会,必须同时设置了不为auto的z-index。fixed和sticky例外,在现代浏览器里它们自己就会创建。
面试官还会怎么问?
Flex 子项不设 position,z-index 会生效吗?
会生效。Flex 和 Grid 容器的子项只要设了不为 auto 的 z-index,就会参与层叠排序并创建层叠上下文,不需要额外写 position: relative。普通块级元素则必须先定位,z-index 才有效。
z-index: 0 和 z-index: auto 有什么区别?
对定位元素来说,两者在外部排序时位置差不多,都在「定位元素」那一层按文档顺序排。区别是 z-index: 0 会创建层叠上下文,子元素被圈在里面;auto 不会,子元素的 z-index 会直接参与外层的比较。
怎么避免项目里 z-index 越写越大?
先给全局层级定一套变量,比如下拉、吸顶、遮罩、弹窗、提示各占一档。弹层统一通过 Portal 挂到 body 下。组件根元素加 isolation: isolate,让内部层级只在组件内比较,这样组件里用 1、2、3 就够了。
参考资料
示例用于理解所注明的运行环境与边界;延伸学习可结合原文中的更多案例。