Published on

堆叠上下文

在构建复杂的用户界面时,我们常常需要控制元素在 Z 轴(垂直于屏幕的轴线)上的层叠顺序。CSS z-index 属性看似是解决这一问题的直接工具,但其行为却常常与直觉相悖,导致“z-index 失效”的困惑。这一现象的根源在于一个更底层的、控制元素渲染顺序的核心机制——堆叠上下文 (Stacking Context)

z-index 与堆叠上下文的创建

z-index 属性用于设定已定位元素 (positioned elements) 及其后代在 Z 轴上的堆叠顺序。

z-index 的生效前提

z-index 属性仅对 position 值不为 static(即 relative, absolute, fixed, sticky)的元素生效。

一个元素的 z-index 值越大,其在堆叠顺序中就越靠前。然而,这个比较并非全局性的,而是被严格限制在同一个堆叠上下文内部。

堆叠上下文的创建

当一个已定位的元素被赋予一个不为 autoz-index 值时,该元素就会创建一个全新的、独立的堆叠上下文

堆叠上下文的核心规则

一旦一个元素创建了自己的堆叠上下文,它就会像一个“黑盒”或“独立的小世界”,其内部的层叠顺序将与外部世界完全隔离。

  • 原子性: 一旦一个元素创建了堆叠上下文,它与其所有后代元素就会被视为一个不可分割的原子单元。浏览器在决定渲染顺序时,首先会在父级堆叠上下文中,根据这些“原子单元”自身的 z-index 来确定它们之间的宏观层级。
  • 局部性: 后代元素的 z-index 值只在其所属的堆叠上下文内部进行比较,用于决定它们与同级兄弟元素的堆叠顺序。
z-index 的非全局性

<div style="position: relative; z-index: 1; background: lightblue; padding: 20px;">
  父元素 (z-index: 1)
  <div style="position: absolute; z-index: 999; background: lightcoral;">
    子元素 1 (z-index: 999)
  </div>
  <div style="position: absolute; z-index: 3; background: lightgreen; top: 40px; left: 40px;">
    子元素 2 (z-index: 3)
  </div>
</div>
<div style="position: relative; z-index: 2; background: gold; margin-top: -50px;">
  父元素的兄弟元素 (z-index: 2)
</div>

渲染结果: “父元素的兄弟元素 (z-index: 2)”会覆盖在“子元素 1 (z-index: 999)”之上。 原因分析:

  1. z-index12 的两个 div 在根堆叠上下文中进行比较,z-index: 2 的胜出,因此它整体位于上层。
  2. z-index9993 的两个子元素,它们只在父元素创建的那个独立的堆叠上下文内部进行比较。z-index: 999 的子元素会覆盖 z-index: 3 的子元素。
  3. 但无论子元素的 z-index 值有多大,它都无法“逃逸”出其父级堆叠上下文的层级。

堆叠上下文的其他触发条件

除了 position + z-index 这一经典组合,现代 CSS 中还有许多其他属性也会创建新的堆叠上下文。

  • 现代推荐:isolation: isolate,这是创建堆叠上下文最纯粹、最推荐的方式。
isolation: isolate 的优势

isolation: isolate 的唯一作用就是创建一个新的堆叠上下文,而没有任何其他的视觉或布局副作用(不像 opacitytransform)。在构建可封装的、独立的 UI 组件(如 React 或 Vue 组件)时,为组件根元素设置此属性,可以有效避免其内部的 z-index 与外部环境意外地相互干扰,是构建健壮组件体系的最佳实践。

  • 其他常见触发器:
    • position 值为 fixedsticky
    • opacity 属性值小于 1 的元素。
    • transform, filter, clip-path, perspective 属性值不为 none 的元素。
    • flexgrid 容器中,z-index 值不为 auto 的子元素。
    • mix-blend-mode 属性值不为 normal 的元素。
    • will-change 指定了任何会创建堆叠上下文的属性。

堆叠上下文 vs. 合成层

这是一个更深层次的性能概念,两者紧密相关但并不等同。

  • 堆叠上下文 (Stacking Context): 是一个 CSS 规范中的概念,它定义了元素的绘制顺序 (painting order)
  • 合成层 (Compositor Layer): 是一个浏览器渲染引擎实现细节,与性能优化相关。浏览器会将某些 DOM 子树(通常是那些即将发生 transformopacity 动画的元素)提升到一个独立的“层”中,交由 GPU 进行处理,以实现高性能动画。
两者的关系

  • 创建一个合成层,其先决条件之一是该元素必须处于一个堆叠上下文中。
  • 但反之不成立:一个元素创建了堆叠上下文,并不意味着它一定会被提升为独立的合成层。
  • 多个堆叠上下文,可能最终只被绘制在同一个合成层上。