发布于 2026-01-06 1 阅读
0

为什么 z-index 不起作用?! - CSS 堆叠上下文详解 什么是绘制顺序? 重新排列 HTML 元素以更改绘制顺序 定位元素和非定位元素的行为不同 理解绘制顺序的更多规则 创建堆叠上下文 {#stacking-contexts} 另一个模态框堆叠上下文示例 堆叠 堆叠上下文 堆叠上下文的问题

为什么 z-index 不起作用?! - CSS 堆叠上下文详解

什么是绘画顺序?

重新排列 HTML 元素以改变绘画顺序

定位元素和非定位元素的行为方式不同

了解更多绘画顺序规则

创建堆叠上下文 {#stacking-contexts}

另一个模态堆叠上下文示例

堆叠 堆叠上下文

上下文堆叠的问题

维度很奇妙。虽然大多数网页应用似乎都专注于 x 轴x和 yy轴,代表用户交互的二维平面,但实际上还有一个z经常被忽略的 x 轴。一些浏览器,例如Microsoft Edge,甚至提供了一种将网站放大到 3D 视图的方法

在 Microsoft Edge 的 3D 视图调试模式下查看 Google 的视图

虽然这很酷,但通过为网页引入第三维度,我们也引入了元素之间相互重叠的可能性。使用 CSS 管理重叠问题非常棘手

当然,你可以使用z-index`\tax`,许多人声称它是管理元素z轴线的简单方法,但它似乎非常脆弱且不一致!

本文并不假定您具备任何预备z-index知识。如果您是通过阅读本文来了解其z-index工作原理,那么您来对地方了。

例如,我们来看一下模态框。模态框是一种用户界面元素,它允许您在页面内容上方显示一个框中的信息。以下是Google 云端硬盘中的一个模态框示例:

一个方框位于 Google 云端硬盘的其他元素上方,显示

尽管模态框可能会给应用程序带来一些用户体验上的难题,但它们仍然是当今许多应用程序中广泛使用的用户界面元素。构建足够实用的模态框可能是一项挑战,但即使不使用 JavaScript,也可以完成一个基本的模态框。

让我们用一些 CSS 和 HTML 来构建一个基本的模态框:

<div>
  <div id="body">
    <p>This is some text, pretend it's an app back here</p>
  </div>
  <div id="modal-container">
    <div id="modal">This is a modal</div>
  </div>
</div>

<style>
#modal-container {
  position: fixed;
  top: 0;
  left: 0;
  height: 100%;
  width: 100%;
  display: flex;
  justify-content: center;
  align-items: center;
  background: rgba(0, 0, 0, 0.5);
}

#modal {
  background: white;
  border: 1px solid black;
  padding: 1rem;
  border-radius: 1rem;
}
</style>
Enter fullscreen mode Exit fullscreen mode

一个位于前景的模态框,其半透明黑色背景会使所有其他元素变暗。

锵锵!🎉 现在我们有了一个相当基本的模态框,可以在其中显示我们想要的任何 HTML 代码。

但假设我们继续完善页面。例如,我们可能希望在footer主页内容下方添加一个元素。

<div>
  <div id="body" style="min-height: 50vh">
    <p>This is some text, pretend it's an app back here</p>
  </div>
  <div id="modal-container">
    <div id="modal">This is a modal</div>
  </div>
  <footer style="min-height: 50vh">App Name</footer>
</div>

<style>
#modal-container {
  position: fixed;
  top: 0;
  left: 0;
  height: 100%;
  width: 100%;
  display: flex;
  justify-content: center;
  align-items: center;
  background: rgba(0, 0, 0, 0.5);
}

#modal {
  background: white;
  border: 1px solid black;
  padding: 1rem;
  border-radius: 1rem;
}

footer {
  position: relative;
  background: lightblue;
  padding: 1rem;
}
</style>
Enter fullscreen mode Exit fullscreen mode

乍一看,这似乎很成功,但让我们来看看渲染后的输出结果:

页脚位于模态框上方,而不是下方。

哎呀!为什么页脚会显示在模态框上方?

各位朋友,模态框之所以显示在页脚下方,是因为所谓的“绘制顺序”机制。

什么是绘画顺序?

DOM 中的“绘制顺序”概念相当复杂,但其要点如下:

浏览器会从 HTML 和 CSS 中获取信息,以确定屏幕上要显示什么内容。虽然我们通常认为这个过程是瞬间完成的,但计算机科学中没有什么是真正瞬时的。

在屏幕上显示 HTML 和 CSS 的过程称为屏幕“绘制”或“绘制”。

在屏幕上绘制内容乍听起来很简单,但想想这其中涉及哪些方面:

给定所有 HTML 和 CSS 代码,找出它们应该放在哪里并显示它们。

这里面有很多细微差别;这些细微差别是由一套严格的规则决定的。

我们稍后会详细介绍绘画的具体规则,但首先让我们来看下面的代码示例:

<div id="container">
  <div id="blue">Blue</div>
  <div id="green">Green</div>
  <div id="purple">Purple</div>
</div>

<style>
#container {
  display: relative;
}

#blue,
#green,
#purple {
  height: 100px;
  width: 100px;
  position: absolute;
  padding: 8px;
  color: white;
  border: 4px solid black;
  border-radius: 4px;
}

#blue {
  background: #0f2cbd;
  left: 50px;
  top: 50px;
}

#green {
  background: #007a70;
  left: 100px;
  top: 100px;
}

#purple {
  background: #5f00b2;
  left: 150px;
  top: 150px;
}
</style>
Enter fullscreen mode Exit fullscreen mode

这里有三个相互重叠的方框。鉴于它们相互重叠,你认为哪个方框优先级更高,至少在视觉上位于其他方框之上

不,真的,猜猜看!别往下看了,看看代码,猜猜看。😊

准备好揭晓答案了吗?

好了,就是这个:

这三个彩色方框从上到下依次是:紫色、绿色、蓝色。

这些彩色方框之所以按现在的顺序排列,是因为它们各自遵循着“绘制顺序”。浏览器遵循着“绘制顺序”的规则,最终确定了这种顺序。

虽然一些 CSS 专业人士可能会认为紫色看起来像是在顶部,这是因为 CSS 的布局顺序,就像其他 CSS 规则一样,但事实并非如此。

注意,当我们重新排列 CSS 规则时,紫色方框似乎仍然保持在“顶部”:

#purple {
  background: #5f00b2;
  left: 150px;
  top: 150px;
}

#green {
  background: #007a70;
  left: 100px;
  top: 100px;
}

#blue {
  background: #0f2cbd;
  left: 50px;
  top: 50px;
}
Enter fullscreen mode Exit fullscreen mode

三个彩色方框从上到下的顺序保持不变:紫色、绿色、蓝色。

如果更改 CSS 顺序不能重新排列框并改变绘制顺序,那么什么方法可以?

出色地...

重新排列 HTML 元素以改变绘画顺序

让我们把之前的HTML代码稍微重新排列一下:

<div id="container">
  <div id="purple">Purple</div>
  <div id="green">Green</div>
  <div id="blue">Blue</div>
</div>
Enter fullscreen mode Exit fullscreen mode

现在,如果我们看一下包装盒的顺序,就会发现……

盒子顺序颠倒了!现在从上到下依次是:蓝色、绿色、紫色。

现在我们的方框高度顺序颠倒了!这是因为决定元素绘制顺序的因素之一是它与其他元素的关系。

定位元素和非定位元素的行为方式不同

这里开始有点复杂了。请慢慢阅读本章;反复阅读这部分内容是正常的。

之前我们为了简单演示使用了absolutely 定位元素,现在让我们退一步,改用margin以下方式定位元素:

<div id="container">
  <div id="purple">Purple</div>
  <div id="green">Green</div>
  <div id="blue">Blue</div>
</div>

<style>
#container {
  display: relative;
}

#container > div:nth-child(1) {
  margin-top: 50px;
  margin-left: 50px;
}

#container > div:nth-child(2) {
  margin-top: -50px;
  margin-left: 100px;
}

#container > div:nth-child(3) {
  margin-top: -50px;
  margin-left: 150px;
}

#blue,
#green,
#purple {
  height: 100px;
  width: 100px;
  padding: 8px;
  color: white;
  border: 4px solid black;
  border-radius: 4px;
}

#blue {
  background: #0f2cbd;
}

#green {
  background: #007a70;
}

#purple {
  background: #5f00b2;
}
</style>
Enter fullscreen mode Exit fullscreen mode

看起来像是似曾相识的输出结果:

从上到下依次是:紫色、绿色、蓝色,这三个颜色相同的方框。

在进行样式设计时,我们希望green当鼠标悬停在方框上时,方框能够向左移动。这很容易用CSS 动画实现;让我们添加它:

#green {
  background: #007a70;
  position: relative;
  left: 0px;
  transition: left 300ms ease-in-out;
}

#green:hover {
  left: 20px;
}
Enter fullscreen mode Exit fullscreen mode

虽然现在当您将鼠标悬停在绿色按钮上时,它会平滑地向左移动,但出现了一个新问题:绿色方框现在位于紫色和蓝色方框的上方。

同样的方框,但绿色的似乎在最上面。

这是因为浏览器会优先绘制已定位的元素,而不是未定位的元素。这意味着我们的relative已定位元素会先被绘制出来,并且在图层中似乎比z未定位元素具有更高的优先级。

了解更多绘画顺序规则

虽然relative定位是告诉浏览器优先绘制某个元素的一种方法,但这远非唯一的方法。以下列出了一些 CSS 规则,可以改变元素的绘制顺序,从优先级最低到最高:

  1. background以下标签html,,:rootbody

  2. background堆叠上下文根元素

读完文章后再来看这一点;现在可能还看不懂。

  1. 带有负值的定位元素z-index

  2. 未定位元素background

  3. float应用了样式但未position应用样式的元素

  4. 未定位inline元素

  5. 非定位、非浮动元素的文本内容,以及其他一些规则

  6. z-index应用或应用了z-index的定位元素0,以及其他一些规则

  7. 元素具有一个z-index1多个

  8. 根据您使用的浏览器,outlines

虽然这包含了根据 CSS 规范绘制顺序的所有步骤,但这些步骤可能还有更多子步骤。总的来说,为了保持列表的可读性,此列表并不完整。

所以,如果我们有以下HTML代码:

<div class="container" style="background: rgba(0, 0, 0, 0.8)">
    <div class="box slate" style="position: relative">Slate</div>
    <div class="box yellow" style="display: inline-block">Yellow</div>
    <div class="box lime" style="float: left">Lime</div>
    <div class="box green" style="">Green</div>
    <div class="box cyan" style="position: relative; z-index: -1">Cyan</div>
</div>
Enter fullscreen mode Exit fullscreen mode

我们会看到,从上到下:

  • 一个slate彩色盒子
  • 一个yellow彩色盒子
  • 一个lime彩色盒子
  • 一个green彩色盒子
  • container背景
  • 一个cyan彩色盒子

如上所述,箱子按顺序排列。

正如我们之前所学,所有这些规则都以 HTML 元素的顺序为准。例如,对于以下 HTML 代码:

<div class="container" style="background: rgba(0, 0, 0, 0.8)">
  <div class="box slate" style="position: relative">Slate</div>
  <div class="box yellow" style="">Yellow</div>
  <div class="box lime" style="position: relative">Lime</div>
  <div class="box cyan" style="">Cyan</div>
</div>
Enter fullscreen mode Exit fullscreen mode

你会看到以下元素顺序:

  • 酸橙
  • 石板
  • 青色
  • 黄色的

一个由积木组成的正方形,展示了上述排列顺序。

这是因为 <div>lime和 <span>由于其位置关系,在 <span>和 <span>slate上具有更高的绘制优先级,但它们仍然在同一级别的优先级和相同的堆叠上下文中按 HTML 顺序排列。yellowcyanrelativez

创建堆叠上下文 {#stacking-contexts}

“好了,今天这本书就读到这里吧。”

你心想,然后躺下睡觉。在梦里,你仍然能听到那本书在对你说话:

[...] 仍然按照 HTML 顺序排列,z优先级相同,并且位于相同的堆叠上下文中。

[...] 在相同的堆叠上下文中

这本书的内容重复出现:

[...] 在相同的堆叠上下文中

你醒来,意识到自己还不理解这句话的意思,于是心想:

事情不可能变得更复杂了。

很遗憾,确实如此。


从本质上讲,堆叠上下文是一个可以z同时沿 - 轴向上或向下移动多个项目的组。

请看以下HTML代码:

<div class="container">
  <div id="top-container" style="position: relative">
    <div class="box slate" style="position: relative">Slate</div>
    <div class="box yellow" style="">Yellow</div>
  </div>
  <div id="bottom-container">
    <div class="box lime" style="position: relative">Lime</div>
    <div class="box cyan" style="">Cyan</div>
  </div>
</div>
Enter fullscreen mode Exit fullscreen mode

你认为这些box字母的顺序会是怎样的?

彩色方框按以下说明的顺序排列

答案是:

  • 石板
  • 酸橙
  • 青色
  • 黄色的

这是因为,尽管父元素top-container有`<div>` 标签position: relative,但 ` box<div>` 标签仍然处于同一个堆叠上下文中。该堆叠上下文遵循之前概述的相同排序规则,这意味着定位后的 `<div>` 标签slatelime box`<div>` 标签的z优先级高于`<div>` 标签cyan和 ` <div>` 标签yellow

准备好迎接反转了吗?

让我们补充z-index以下内容top-container

<div class="container">
  <div style="position: relative; z-index: 1">
    <div class="box slate" style="position: relative">Slate</div>
    <div class="box yellow" style="">Yellow</div>
  </div>
  <div>
    <div class="box lime" style="position: relative">Lime</div>
    <div class="box cyan" style="">Cyan</div>
  </div>
</div>
Enter fullscreen mode Exit fullscreen mode

你觉得他们的顺序会是怎样的?

彩色方框按以下方式重新排列。

  • 石板
  • 黄色的
  • 酸橙
  • 青色

这是因为,实际上,我们这里排序的并不是字母boxes。相反,我们排序的是字母top-containers bottom-container div然后才是字母boxes,就像这样:

  • top-container
    • slate
    • yellow
  • bottom-container
    • lime
    • cyan

这种情况仅在我们添加z-index元素时发生top-container,因为那时会创建一个新的堆叠上下文。创建该上下文时,z由于与之前相同的排序规则,我们将其提升到更高的 - 轴位置。

z请记住,堆叠上下文是指当父元素的- 轴位置改变时,一组元素作为一个整体一起移动。

堆叠上下文会在以下情况下创建:

此列表并不详尽,但包含了创建堆叠上下文时的大部分要点。

值得一提的是,如果创建了堆叠上下文,则创建该堆叠上下文的元素将按优先级z轴排序处理。

例如,如果您有:

<div>
    <div style="position: absolute; top: 0; background: white">Absolute</div>
    <div style="opacity: 0.99; background: white">Opacity</div>
</div>
Enter fullscreen mode Exit fullscreen mode

然后,由于 HTML 序列的顺序,它会在“不透明度”上方显示“绝对”;尽管定位元素通常比 HTML 序列具有更高的优先级,但仍然会出现这种情况。

如果我们opacity: 0.99从中移除"Opacity" div,那么"Absolute“将位于顶部。

另一个模态堆叠上下文示例

让我们回顾一下目前为止我们学到了什么。

我们从上一节中得知,position: relative大于z-index1应该创建一个堆叠上下文。

根据这些信息,我们预期以下代码会渲染出什么结果?

<header
  class="container-box"
  style="position: relative; z-index: 1; background: #007a70"
>
  Header
</header>
<footer
  class="container-box"
  style="position: relative; z-index: 2; background: #0f2cbd"
>
  Footer
</footer>
<style>
.container-box {
  color: white;
  padding: 1rem;
  border-radius: 0.5rem;
  border: 0.25rem solid black;
}
</style>
Enter fullscreen mode Exit fullscreen mode

没有什么特别令人惊讶的地方;它会渲染一个绿色的矩形,位于一个蓝色矩形的上方,分别显示“页眉”和“页脚”字样。

两个矩形的表示方法与上一句所述相同。

现在,假设我们想在页脚上方显示一个模态框,向用户显示他们可能认为相关的信息。

position: absolute; z-index: 99那么,如果我们在 ? 中添加上述模态框,你认为会发生什么<header>

<header
  class="container-box"
  style="position: relative; z-index: 1; background: #007a70"
>
  Header
  <div
    id="modal"
    class="container-box"
    style="
      position: absolute;
      z-index: 99;
      background: #007a70;
      width: 100%;
      box-sizing: border-box;
      bottom: -3.75rem;
      left: 0;
    "
  >
    Modal in header
  </div>
</header>
<footer
  class="container-box"
  style="position: relative; z-index: 2; background: #0f2cbd"
>
  Footer
</footer>
<style>
.container-box {
  color: white;
  padding: 1rem;
  border-radius: 0.5rem;
  border: 0.25rem solid black;
}
</style>
Enter fullscreen mode Exit fullscreen mode

你可能会惊讶地发现,一切都没有改变——模态框似乎没有显示在任何地方,而是像以前一样显示相同的“页眉”和“页脚”元素:

和之前一样的两个矩形

但是,如果我们把footer代码改成如下形式:

<footer
  class="container-box"
  style="position: relative; z-index: 0; background: #0f2cbd"
>
  Footer
</footer>
Enter fullscreen mode Exit fullscreen mode

我们唯一改变的是z-index10

然后,Footer正如预期的那样,我们突然看到了上方的模态框:

两个绿色矩形

为什么会发生这种情况?

header这是因为我们在执行操作时创建了一个堆叠上下文position: relative; z-index: 1。这反过来又将包含元素放置id="modal" div在堆叠上下文中header

因此,id="modal"'s不适用于堆叠上下文之外,并且从 的角度来看,z-index下面的元素组header被视为z-index: 1footer

下面这张图说明了我的意思:

头部和模态框位于同一堆叠上下文中,z-index 为 1。

你会看到两者<header><div id="modal">包含在一个名为 的框中z-index: 1。这是因为它们都包含在header的堆叠上下文中。

即使id="modal"具有,它也仅适用于堆叠上下文,这意味着它永远不能放置在堆叠上下文中z-index元素之上99headerfooterz-indexheader

堆叠 堆叠上下文

让我们把前面的例子推向一个极端。

假设我们希望模态框拥有自己的标题栏,允许用户滚动浏览模态框的内容,同时标题栏始终显示在顶部。

<header
  class="container-box"
  style="position: relative; z-index: 1; background: #007a70"
>
  Header
  <div
    id="modal"
    class="container-box"
    style="
      position: absolute;
      z-index: 99;
      background: #007a70;
      width: 100%;
      box-sizing: border-box;
      top: 50%;
      left: 0;
      overflow: auto;
      height: 10rem;
      padding: 0;
    "
  >
    <div
      style="
        background: #B92015;
        position: sticky;
        z-index: 10;
        top: 0;
        left: 0;
        width: 100%;
        padding: 1rem;
        box-sizing: border-box;
      "
      id="modal-title"
    >
      Modal Title
    </div>
    <div style="padding: 1rem; height: 10rem" id="modal-body">Modal in header</div>
  </div>
</header>
<footer
  class="container-box"
  style="position: relative; z-index: 2; background: #0f2cbd"
>
  Footer
</footer>
Enter fullscreen mode Exit fullscreen mode

起初,由于页脚位于模态框上方(原因我们之前已经阐述过),所以看起来可能有点奇怪。

页脚明显位于模态框上方

但是,如果我们暂时移除页脚,就可以如预期那样在页眉上方看到模态框渲染:

模态框位于标题上方。

你可能想知道:

为什么模态框的标题不在页脚上方?毕竟,一个z-index标题10应该比z-index两个标题都重要。

这同样是因为创建了堆叠上下文。在这种情况下,不仅会header创建一个堆叠上下文,而且id="modal"也会创建一个!因为modal-header在堆叠上下文中modal,而modal在堆叠上下文中header,所以它仍然在下方footer

我们可以在这里看到这些堆叠上下文的样子:

页眉及其所有子元素都位于堆叠上下文中,其 z-index 为 1。页脚的 z-index 为 2。

没错;你可以在其他堆叠上下文中嵌套堆叠上下文。🤯

上下文堆叠的问题

那么问题出在哪里呢?如果你能控制 HTML,你可以简单地将 <div> 标签移出modal<div> 元素header,并将其放在 <div> 元素之后footer。这样就能使其与页脚和之前的所有内容重叠,只要它的 <div> 属性值不高z-index,对吧?

没错……

啊哈!

……但有一个重要的前提条件。

在现代网页开发中,精确控制 HTML 元素的渲染位置说起来容易做起来难。

特别是,如果您使用 React、Angular 或 Vue 等框架,您的组件可能看起来像这样:

<Header/>
<Footer/>
Enter fullscreen mode Exit fullscreen mode

Header组件包含以下内容

React

const Header = () => {
  const [isDialogOpen, setDialogOpen] = useState(false);
  return <>
    <button onClick={() => setDialogOpen(true)}>Open dialog</button>
    {isDialogOpen && <Dialog/>}
  </>
}
Enter fullscreen mode Exit fullscreen mode

@Component({
    selector: 'Header',
    standalone: true,
    template: `
        <button (click)="openDialog()">Open dialog</button>
        <Dialog *ngIf="isDialogOpen"></Dialog>
    `
})
class HeaderComponent {
    isDialogOpen = false;

    openDialog() {
       this.isDialogOpen = true;
    }
}
Enter fullscreen mode Exit fullscreen mode

维尤

<!-- Header.vue -->
<template>
    <button @click="openDialog()">Open dialog</button>
  <Dialog v-if="isDialogOpen"></Dialog>
</template>

<script setup>
import {ref} from 'vue';

const isDialogOpen = ref(false);

function openDialog() {
  isDialogOpen.value = true;
}
</script>
Enter fullscreen mode Exit fullscreen mode

在这种情况下,如果不将状态移出对话框Dialog,您将如何在组件之后渲染内容Footer

答案是?JavaScript Portals。

React 有createPortalCDK Portal API ,Angular 有 CDK Portal APIVue 有自己的<Teleport>组件

想了解更多关于 React、Angular 和 Vue 如何解决这个问题的信息吗?敬请关注我即将出版的新书《框架实战指南》,本书同时讲解了这三个框架,包括 Portals。

想了解更多关于“堆叠上下文”的信息,我建议阅读以下资源:

文章来源:https://dev.to/this-is-learning/why-is-z-index-not-working-explaining-css-stacking-context-34da