为什么 z-index 不起作用?! - CSS 堆叠上下文详解
什么是绘画顺序?
重新排列 HTML 元素以改变绘画顺序
定位元素和非定位元素的行为方式不同
了解更多绘画顺序规则
创建堆叠上下文 {#stacking-contexts}
另一个模态堆叠上下文示例
堆叠 堆叠上下文
上下文堆叠的问题
维度很奇妙。虽然大多数网页应用似乎都专注于 x 轴x和 yy轴,代表用户交互的二维平面,但实际上还有一个z经常被忽略的 x 轴。一些浏览器,例如Microsoft Edge,甚至提供了一种将网站放大到 3D 视图的方法:
虽然这很酷,但通过为网页引入第三维度,我们也引入了元素之间相互重叠的可能性。使用 CSS 管理重叠问题非常棘手。
当然,你可以使用z-index`\tax`,许多人声称它是管理元素z轴线的简单方法,但它似乎非常脆弱且不一致!
本文并不假定您具备任何预备
z-index知识。如果您是通过阅读本文来了解其z-index工作原理,那么您来对地方了。
例如,我们来看一下模态框。模态框是一种用户界面元素,它允许您在页面内容上方显示一个框中的信息。以下是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>
锵锵!🎉 现在我们有了一个相当基本的模态框,可以在其中显示我们想要的任何 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>
乍一看,这似乎很成功,但让我们来看看渲染后的输出结果:
哎呀!为什么页脚会显示在模态框上方?
各位朋友,模态框之所以显示在页脚下方,是因为所谓的“绘制顺序”机制。
什么是绘画顺序?
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>
这里有三个相互重叠的方框。鉴于它们相互重叠,你认为哪个方框优先级更高,至少在视觉上位于其他方框之上?
不,真的,猜猜看!别往下看了,看看代码,猜猜看。😊
准备好揭晓答案了吗?
好了,就是这个:
这些彩色方框之所以按现在的顺序排列,是因为它们各自遵循着“绘制顺序”。浏览器遵循着“绘制顺序”的规则,最终确定了这种顺序。
虽然一些 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;
}
如果更改 CSS 顺序不能重新排列框并改变绘制顺序,那么什么方法可以?
出色地...
重新排列 HTML 元素以改变绘画顺序
让我们把之前的HTML代码稍微重新排列一下:
<div id="container">
<div id="purple">Purple</div>
<div id="green">Green</div>
<div id="blue">Blue</div>
</div>
现在,如果我们看一下包装盒的顺序,就会发现……
现在我们的方框高度顺序颠倒了!这是因为决定元素绘制顺序的因素之一是它与其他元素的关系。
定位元素和非定位元素的行为方式不同
这里开始有点复杂了。请慢慢阅读本章;反复阅读这部分内容是正常的。
之前我们为了简单演示使用了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>
看起来像是似曾相识的输出结果:
在进行样式设计时,我们希望green当鼠标悬停在方框上时,方框能够向左移动。这很容易用CSS 动画实现;让我们添加它:
#green {
background: #007a70;
position: relative;
left: 0px;
transition: left 300ms ease-in-out;
}
#green:hover {
left: 20px;
}
虽然现在当您将鼠标悬停在绿色按钮上时,它会平滑地向左移动,但出现了一个新问题:绿色方框现在位于紫色和蓝色方框的上方。
这是因为浏览器会优先绘制已定位的元素,而不是未定位的元素。这意味着我们的relative已定位元素会先被绘制出来,并且在图层中似乎比z未定位元素具有更高的优先级。
了解更多绘画顺序规则
虽然relative定位是告诉浏览器优先绘制某个元素的一种方法,但这远非唯一的方法。以下列出了一些 CSS 规则,可以改变元素的绘制顺序,从优先级最低到最高:
-
background以下标签的:html,,:rootbody -
background堆叠上下文根元素
读完文章后再来看这一点;现在可能还看不懂。
-
带有负值的定位元素
z-index -
未定位元素
background -
float应用了样式但未position应用样式的元素 -
未定位
inline元素 -
非定位、非浮动元素的文本内容,以及其他一些规则
-
未
z-index应用或应用了z-index的定位元素0,以及其他一些规则 -
元素具有一个
z-index或1多个 -
根据您使用的浏览器,
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>
我们会看到,从上到下:
- 一个
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>
你会看到以下元素顺序:
- 酸橙
- 石板
- 青色
- 黄色的
这是因为 <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>
你认为这些box字母的顺序会是怎样的?
答案是:
- 石板
- 酸橙
- 青色
- 黄色的
这是因为,尽管父元素top-container有`<div>` 标签position: relative,但 ` box<div>` 标签仍然处于同一个堆叠上下文中。该堆叠上下文遵循之前概述的相同排序规则,这意味着定位后的 `<div>` 标签slate和lime 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>
你觉得他们的顺序会是怎样的?
- 石板
- 黄色的
- 酸橙
- 青色
这是因为,实际上,我们这里排序的并不是字母boxes。相反,我们排序的是字母top-containers bottom-container div,然后才是字母boxes,就像这样:
top-containerslateyellow
bottom-containerlimecyan
这种情况仅在我们添加z-index元素时发生top-container,因为那时会创建一个新的堆叠上下文。创建该上下文时,z由于与之前相同的排序规则,我们将其提升到更高的 - 轴位置。
z请记住,堆叠上下文是指当父元素的- 轴位置改变时,一组元素作为一个整体一起移动。
堆叠上下文会在以下情况下创建:
z-index应用于已定位元素-
z-index适用于元素的grid子flex元素 -
元素
opacity小于1 -
具有以下任一属性的元素:
此列表并不详尽,但包含了创建堆叠上下文时的大部分要点。
值得一提的是,如果创建了堆叠上下文,则创建该堆叠上下文的元素将按优先级z轴排序处理。
例如,如果您有:
<div>
<div style="position: absolute; top: 0; background: white">Absolute</div>
<div style="opacity: 0.99; background: white">Opacity</div>
</div>
然后,由于 HTML 序列的顺序,它会在“不透明度”上方显示“绝对”;尽管定位元素通常比 HTML 序列具有更高的优先级,但仍然会出现这种情况。
如果我们opacity: 0.99从中移除"Opacity" div,那么"Absolute“将位于顶部。
另一个模态堆叠上下文示例
让我们回顾一下目前为止我们学到了什么。
我们从上一节中得知,position: relative大于z-index号1应该创建一个堆叠上下文。
根据这些信息,我们预期以下代码会渲染出什么结果?
<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>
没有什么特别令人惊讶的地方;它会渲染一个绿色的矩形,位于一个蓝色矩形的上方,分别显示“页眉”和“页脚”字样。
现在,假设我们想在页脚上方显示一个模态框,向用户显示他们可能认为相关的信息。
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>
你可能会惊讶地发现,一切都没有改变——模态框似乎没有显示在任何地方,而是像以前一样显示相同的“页眉”和“页脚”元素:
但是,如果我们把footer代码改成如下形式:
<footer
class="container-box"
style="position: relative; z-index: 0; background: #0f2cbd"
>
Footer
</footer>
我们唯一改变的是
z-index从1到0
然后,Footer正如预期的那样,我们突然看到了上方的模态框:
为什么会发生这种情况?
header这是因为我们在执行操作时创建了一个堆叠上下文position: relative; z-index: 1。这反过来又将包含元素放置id="modal" div在堆叠上下文中header。
因此,id="modal"'s不适用于堆叠上下文之外,并且从 的角度来看,z-index下面的元素组header被视为。z-index: 1footer
下面这张图说明了我的意思:
你会看到两者<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>
起初,由于页脚位于模态框上方(原因我们之前已经阐述过),所以看起来可能有点奇怪。
但是,如果我们暂时移除页脚,就可以如预期那样在页眉上方看到模态框渲染:
你可能想知道:
为什么模态框的标题不在页脚上方?毕竟,一个
z-index标题10应该比z-index两个标题都重要。
这同样是因为创建了堆叠上下文。在这种情况下,不仅会header创建一个堆叠上下文,而且id="modal"也会创建一个!因为modal-header在堆叠上下文中modal,而modal在堆叠上下文中header,所以它仍然在下方footer。
我们可以在这里看到这些堆叠上下文的样子:
没错;你可以在其他堆叠上下文中嵌套堆叠上下文。🤯
上下文堆叠的问题
那么问题出在哪里呢?如果你能控制 HTML,你可以简单地将 <div> 标签移出
modal<div> 元素header,并将其放在 <div> 元素之后footer。这样就能使其与页脚和之前的所有内容重叠,只要它的 <div> 属性值不高z-index,对吧?
没错……
啊哈!
……但有一个重要的前提条件。
在现代网页开发中,精确控制 HTML 元素的渲染位置说起来容易做起来难。
特别是,如果您使用 React、Angular 或 Vue 等框架,您的组件可能看起来像这样:
<Header/>
<Footer/>
Header组件包含以下内容:
React
const Header = () => {
const [isDialogOpen, setDialogOpen] = useState(false);
return <>
<button onClick={() => setDialogOpen(true)}>Open dialog</button>
{isDialogOpen && <Dialog/>}
</>
}
角
@Component({
selector: 'Header',
standalone: true,
template: `
<button (click)="openDialog()">Open dialog</button>
<Dialog *ngIf="isDialogOpen"></Dialog>
`
})
class HeaderComponent {
isDialogOpen = false;
openDialog() {
this.isDialogOpen = true;
}
}
维尤
<!-- 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>
在这种情况下,如果不将状态移出对话框Dialog,您将如何在组件之后渲染内容Footer?
答案是?JavaScript Portals。
React 有createPortalCDK Portal API ,Angular 有 CDK Portal API,Vue 有自己的<Teleport>组件。
想了解更多关于 React、Angular 和 Vue 如何解决这个问题的信息吗?敬请关注我即将出版的新书《框架实战指南》,本书同时讲解了这三个框架,包括 Portals。
文章来源:https://dev.to/this-is-learning/why-is-z-index-not-working-explaining-css-stacking-context-34da想了解更多关于“堆叠上下文”的信息,我建议阅读以下资源:



















