在前端开发中,我们经常会遇到元素互相遮挡的问题。为了控制谁在上面、谁在下面,最直观的反应通常是顺手加上 z-index: 9999。但现实往往比较骨感,有时不论把 z-index 加到多大,元素依然被死死压在下层。
要彻底解决这类“离奇”的遮挡问题,我们需要跳出单纯的 z-index,深入理解 CSS 渲染三维空间中的核心概念:层叠上下文 和 层叠顺序。
你可以把网页的渲染过程想象成在一个三维坐标系(X、Y、Z 轴)中作画,其中 Z 轴垂直于屏幕指向用户。层叠上下文就是 Z 轴上的一个“独立结界”或“图层组”。
在这个“图层组”内部,所有的子元素会按照一定的规则重新梳理排序。更重要的是,层叠上下文具有绝对的隔离性:一个结界内部的元素,无论它的 z-index 有多大,都无法突破其父级层叠上下文的限制去和结界外的元素抗衡。这就是常说的层叠“拼爹”原则。
页面默认由 <html> 根元素形成一个基础的层叠上下文。除此之外,当一个元素满足以下任一条件时,它就会“自立门户”,创建一个新的层叠上下文:
position 值为 absolute 或 relative,并且 z-index 值不为 auto。position 值为 fixed 或 sticky(此时无需设定 z-index 就会触发)。z-index 配合):
opacity 的值小于 1。transform、filter、perspective、clip-path、mask 等值不为 none。mix-blend-mode 值不为 normal。will-change 指定了上述任意属性。z-index 值不为 auto。一旦元素的层叠上下文被建立,其内部的子元素在发生重叠时,会严格遵循一套固定的“地位高低”顺序。从底(最靠后,离用户最远)到顶(最靠前,离用户最近)依次为:
background 和 border。z-index 的子层叠上下文或定位元素。<span> 等)。z-index: 0 或 z-index: auto 的定位元素(不产生上下文时)。z-index 的定位元素,值越大层级越高。💡 小细节:文字内容所在的行内盒子(层级 5),其地位比块级盒子(层级 3)和浮动盒子(层级 4)都要高。这是因为网页设计的初衷是“内容优先”,文字绝不能轻易被背景色块或浮动元素遮挡。
z-index: 9999 会失效?基于前面的概念,当我们遇到 z-index 无效时,背后的逻辑不外乎以下三种情况:
z-index 属性只对定位元素(position 值为非 static)以及 Flex/Grid 容器的子元素生效。如果一个普通元素只写了 z-index: 9999,它依然会被同级别的定位元素覆盖。
这是最常见的场景。假设有两个父容器 A 和 B,它们都各自创建了层叠上下文,但 A 的整体层级低于 B。那么哪怕 A 内部的子元素设置了 z-index: 999999,B 内部的子元素只设置了 z-index: 1,A 的子元素也永远会被 B 的子元素压在底部。
排查关键:在比较层级时,是优先按照它们所处的父级层叠上下文的层级来决定的。
有时候你并没有改变元素的定位,仅仅是为了给父元素加一个半透明效果(opacity: 0.9),或者加了一个动画变形(transform: scale(1)),导致该父元素意外地变成了一个全新的层叠上下文。这样一来,它内部子元素的 z-index 提升范围就被彻底“锁死”在了这个父容器内部,再也无法去遮盖外部的元素了。
Q:能详细说说层叠上下文吗?为什么有时候设置了特别大的 z-index 还依然会被遮挡?
回答思路:
z-index 并非全局绝对生效,它的作用范围受限于 CSS 三维空间中的层叠上下文概念。z-index、Flex/Grid 结合 z-index 之外,重点强调用到现代 CSS3 属性(如 opacity < 1,transform 不为 none)会隐式创建上下文,这通常也是导致线上 bug 的暗坑。position(static 无效)。transform、opacity、overflow 等属性意外生成了堆叠上下文,从而导致层级被局限在父容器内部。