CSS 教程 / CSS z-index 与堆叠上下文

z-index 控制在相关堆叠上下文中的堆叠,而不是给每个元素分配全局深度。具有较大 z-index 的子元素仍可能位于更高祖先上下文中的元素之下,并且 z-index 无法撤销溢出裁剪。

将定位与堆叠顺序分开

要比较重叠顺序,这两个元素必须实际占据相同的屏幕区域。如果它们完全位于左右或上下,则改变 z-index 不会产生任何效果。首先,在开发者工具中检查元素的矩形位置,并确定是否存在任何重叠区域。有关重叠元素的定位标准,请检查 CSS position: relative、absolute、fixed 和 sticky 的空间和坐标标准。

位置决定了坐标标准和流参与,而 z-index 决定了重叠时的前后顺序。用 top 更改坐标和用 z-index 更改顺序是不同的任务。下面的例子固定了徽章和卡片的位置,然后只更改父元素的 z-index,这样你就可以在不混淆的情况下比较这两个功能。

z-index 可以应用于相对(relative)或绝对(absolute)定位的元素,也可以用于 Flex 项和 Grid 项,即使其 position 为 static。如果你只是给普通的一般流块分配数字并期望有效果,应用条件可能不被满足。你可以在 MDN 的 z-index 描述中查看目标和数值的含义 。

把堆叠上下文作为整体比较

堆叠上下文将其后代作为一组处理,再参与外部堆叠比较。如果左侧的父元素具有z-index: 1,而右侧的卡片具有z-index: 2,则位于左侧上下文中的带有z-index: 9999的子徽章不会与右侧卡片直接竞争。父元素组仍然在其下方。

Left parent context: z-index 1
└ Child badge: z-index 9999
Right card context: z-index 2

Outside, compare parent 1 and card 2.

将左侧父元素提升到z-index: 3会使其整个上下文位于右侧卡片之上;右侧卡片的值仍为 2。子徽章保持自己的9999值,但在重叠中变为可见。祖先和子元素的数值不会相加以产生如10000的全局值。参见MDN:堆叠上下文。

识别创建上下文的声明

代表性条件 创建一个新的上下文 检查内容
position 为 relative 或 absolute,且 z-index 不为 auto 生成 0 也是一个数字,与 auto 不同。
position 为 fixed 或 sticky 生成 即使 z-index 是 auto 也创建上下文
Flex 或 Grid 项 + 非 auto z-index 生成 可能不需要 position
opacity 小于 1 生成 影响组关系以及可见性
transform 不是 none 生成 即使 translateX(0) 没有产生位移,也不等同于 transform: none
isolation: isolate 生成 如何有意创建一个独立的上下文

上表列出了常见条件,并不是所有创建条件的列表。你必须区分仅指定 relative 而 z-index 为 auto 的情况,以及为 relative 指定 z-index: 0 的情况。如果重叠发生了变化,不仅要检查 z-index 数值,还要检查新添加到祖先元素的透明度或变换。

在相同的上下文中,你可以使用满足应用条件的元素的 z-index 数值来比较前后顺序。然而,由于整体 CSS 绘制顺序有多个层次,包括负层级、正常流和定位元素,因此不要把所有元素的 auto 都视为等于 0,也不要仅根据数字来计算整体页面顺序。学习练习将条件限制为正数和明确的父级上下文。

比较父元素和子元素的 z-index 值

将下面的代码保存为 stacking-context.html ,以 UTF-8 编码保存并在浏览器中打开。徽章位于 .scene 顶部以下 80–144px 的范围,右侧的卡片放置在 110-230px 区域,垂直重叠为 34px。左右位置使用相对于父元素宽度的比例,以在移动端保持重叠效果。父元素和子元素按 border-box 计算,并配置以防止不必要的内容溢出容器。

<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>stacking-context.html</title>
  <style>
* { box-sizing: border-box; }
body { margin: 16px; font: 16px/1.6 sans-serif; color: #243447; }
h1 { font-size: 24px; } h2 { font-size: 20px; margin-top: 32px; }
a { color: #145f8f; } a:focus-visible { outline: 3px solid #b4470b; outline-offset: 3px; }
.scene { position: relative; width: min(100%, 520px); height: 300px; background: #f1f4f7; }
 .left { position: absolute; z-index: 1; left: 0; top: 20px; width: 60%; height: 180px;
 padding: 16px; background: #ffe3b5; }
 .badge { position: absolute; z-index: 9999; left: 70%; top: 60px; width: 50%; height: 64px;
 padding: 8px; background: #f5a7cc; }
 .right { position: absolute; z-index: 2; left: 50%; top: 110px; width: 45%; height: 120px;
 padding: 12px; background: #9cd4f2; }
 .raised .left { z-index: 3; }
  </style>
</head>
<body>
<h1>Child and parent z-index values</h1><h2>Parent: 1; right card: 2</h2><div class="scene original"><div class="left">Left parent<div class="badge">Child badge<br>9999</div></div><div class="right">Right card<br>z-index: 2</div></div><h2>Parent: 3; right card: 2</h2><div class="scene raised"><div class="left">Left parent<div class="badge">Child badge<br>9999</div></div><div class="right">Right card<br>z-index: 2</div></div>
</body>
</html>

在第一个部分,重叠的粉色徽章被蓝色卡片遮挡,在第二个部分,粉色徽章显示在蓝色卡片上方。两者之间唯一的区别是左侧父元素的 1 和 3。下图用图解方式比较了两种状态,保持示意位置不变。这不是浏览器截图。

两幅位置相同的示意图:父级上下文为 1 时,数值为 2 的卡片覆盖子元素 9999 的一部分;父级改为 3 后,子元素出现在卡片前方。

  1. 仅将第一部分中的 .badge 数字增加到 99999。父元素 1 和卡片 2 之间的关系保持不变,因此重叠部分继续被卡片覆盖。然后将数字恢复为 9999。
  2. 添加规则 .original .left { z-index: 3; }。徽章会在坐标不变的情况下显示在卡片前方。确认后,移除新增的规则。
  3. 在原来的第一部分中,仅将 .left 的 z-index 改为 auto。在此示例中,如果没有其他建立上下文的属性,左侧父元素不会创建独立的上下文,因此徽章的上下文将与外部卡片进行比较。确保徽章在前面可见,并将其恢复为 1。
  4. 缩小或放大屏幕以确保徽章和卡片仍然重叠。由于当坐标分开时数值变化不可见,因此必须在保持重叠区域的情况下进行比较。

区分溢出裁剪和堆叠

如果祖先元素使用 overflow: hidden 截断内容,则其边界之外的部分仅靠高 z-index 无法再次可见。如果它在顺序上位于前面但不可见,则需要确定它是被其他元素覆盖还是在祖先边界被截断。将以下代码保存为 stacking-clip.html 并运行。即使增加 z-index,如果它在父边界被截断,请检查 CSS 溢出、长文本换行和省略号 的截断条件。

<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>stacking-clip.html</title>
  <style>
* { box-sizing: border-box; }
body { margin: 16px; font: 16px/1.6 sans-serif; color: #243447; }
h1 { font-size: 24px; } h2 { font-size: 20px; margin-top: 32px; }
a { color: #145f8f; } a:focus-visible { outline: 3px solid #b4470b; outline-offset: 3px; }
.frame { position: relative; width: min(100%, 260px); height: 100px; overflow: hidden;
 border: 2px solid #789; background: #e8f5de; }
 .popup { position: absolute; z-index: 9999; left: 80%; top: 24px; width: 90px; height: 60px;
 padding: 8px; background: #f5a7cc; }
  </style>
</head>
<body>
<h1>A box that stays clipped at a higher z-index</h1><div class="frame">Parent boundary<div class="popup">Badge<br>9999</div></div><p>The right side of the badge extends beyond its parent's boundary.</p>
</body>
</html>
  1. 确保徽章的右侧在父元素边界处被截断。即使将 z-index 改为 99999,被截断的部分也不会显示。将数字恢复为 9999。
  2. 只改变 .frame 的 overflow 为 visible。其他坐标和数字保持不变,但边界外的区域可见。对比后,将其恢复为 hidden。

在真实屏幕上,移除 overflow 可能会影响其他用途,例如滚动和圆角。首先确定是否需要截断,以及弹出层是否应在边界之外,如果需要,将结构移动出截断祖先,或根据情况考虑单独的显示方式。不应仅因为隐藏菜单就一次性更改所有父元素的 overflow。

遵循诊断序列

  1. 检查元素是否实际存在,以及是否由于 display、visibility 或 opacity 隐藏。
  2. 检查两个元素的矩形位置,查看是否存在实际的重叠区域。
  3. 向上遍历目标元素及其祖先,查找创建堆叠上下文的声明。确定比较的公共父元素以对比竞争的上下文。
  4. 确定需要修改的是子级还是父级上下文的 z-index。同时检查受影响的同级元素,并仅在必要范围内调整其值。
  5. 如果即使在更改顺序后仍然看不到它,请检查截断情况,如溢出·剪裁和其他坐标标准。完成比较后,将临时修改的值恢复为原始值。

普通元素的 z-index 不应被认为是通过简单增加数值就能克服单独显示层(如顶层对话框)的方法。在排查默认卡片和徽章时,首先要了解实际的父级上下文和截断情况。与其不断增加数值,不如通过检查哪些组进行比较,即使是小值也能配置出预期顺序。