您是否曾经在 CSS 中的某个元素上设置了 z-index: 99999,并且它没有出现在其他元素之上?假设所有不同的元素都设置为较低的值或根本没有设置,那么大的值应该很容易将该元素视觉地放置在其他元素之上。 网页通常表示在二维空间中;然而,通过应用特定的 CSS 属性,引入了一个假想的 z 轴平面来传达深度。该平面垂直于屏幕,用户可以从该平面感知元素的顺序,一个在另一个之上。假想的 z 轴(用户对堆叠元素的感知)背后的想法是,创建它的 CSS 属性组合在一起形成我们所说的堆叠上下文。 我们将讨论元素如何在网页上“堆叠”、控制堆叠顺序的因素以及在需要时“取消堆叠”元素的实用方法。 关于堆叠上下文 将您的网页想象成一张桌子。添加 HTML 元素时,您会将一张张纸一张一张地放在桌子上。最后放置的纸张相当于最近添加的 HTML 元素,并且它位于之前放置的所有其他纸张之上。这是正常的文档流,即使对于嵌套元素也是如此。桌面本身代表根堆叠上下文,由 元素形成,其中包含所有其他文件夹。 现在,特定的 CSS 属性开始发挥作用。 位置(带有 z 索引)、不透明度、变换和包含等属性就像文件夹一样。该文件夹采用一个元素及其所有子元素,从主堆栈中提取它们,并将它们分组到一个单独的子堆栈中,创建我们所说的堆栈上下文。对于定位元素,当我们声明 auto 以外的 z-index 值时,就会发生这种情况。对于不透明度、变换和过滤器等属性,当应用特定值时会自动创建堆叠上下文。
尝试理解这一点:一旦一张纸(即子元素)位于文件夹(即父元素的堆叠上下文)内,它就永远无法退出该文件夹或放置在不同文件夹中的纸之间。它的 z-index 现在仅在其自己的文件夹内相关。
在下图中,纸张 B 现在位于文件夹 B 的堆叠上下文中,并且只能与文件夹中的其他纸张一起订购。
如果愿意的话,想象一下您的桌子上有两个文件夹:
.folder-a { z 索引: 1; } .folder-b { z 索引: 2; }
让我们稍微更新一下标记。文件夹 A 内部是一个特殊页面,z-index:9999。文件夹 B 内部是一个普通页面,z-index:5。
.special-page { z-index:9999; } .plain-page { z 索引: 5; }
哪一页在最上面? 它是文件夹 B 中的 .plain-page。浏览器会忽略子文件并首先堆叠两个文件夹。它看到文件夹 B (z-index: 2) 并将其放置在文件夹 A (z-index: 1) 的顶部,因为我们知道 2 大于 1。同时,设置为 z-index: 9999 的 .special-page 页面位于堆栈底部,即使其 z-index 设置为可能的最高值。 堆叠上下文也可以嵌套(文件夹内的文件夹),创建“家谱”。同样的原则也适用:孩子永远无法逃脱父母的文件夹。 现在您已经了解了堆叠上下文的行为类似于对图层进行分组和重新排序的文件夹,那么值得一问的是:为什么某些属性(例如变换和不透明度)会创建新的堆叠上下文? 事情是这样的:这些属性不会因为它们的外观而创建堆叠上下文;他们这样做是因为浏览器在幕后的工作方式。当您应用变换、不透明度、滤镜或透视时,您是在告诉浏览器:“嘿,这个元素可能会移动、旋转或淡出,所以做好准备!”
当您使用这些属性时,浏览器会创建一个新的堆栈上下文来更有效地管理渲染。这允许浏览器独立处理动画、转换和视觉效果,从而减少重新计算这些元素如何与页面其余部分交互的需要。可以把它想象成浏览器在说:“我将单独处理这个文件夹,这样我就不必在每次其中的内容发生变化时重新调整整个桌面。” 但有副作用。一旦浏览器将一个元素提升到自己的层中,它必须“展平”其中的所有内容,创建一个新的堆叠上下文。这就像把一个文件夹从桌子上拿下来单独处理一样;该文件夹中的所有内容都会被分组,浏览器现在在决定哪些内容位于哪些内容之上时将其视为一个单元。 因此,即使变换和不透明度属性似乎不会影响元素在视觉上堆叠的方式,但它们确实会影响,而且这是为了性能优化。出于类似的原因,其他几个 CSS 属性也可以创建堆叠上下文。如果您想深入了解,MDN 提供了完整的列表。有相当多,这只是说明了在不知情的情况下无意中创建堆栈上下文是多么容易。 “拆垛”问题 出现堆叠问题的原因有很多,但有些原因比其他原因更常见。模态组件是一种经典模式,因为它们需要将组件切换为在所有其他元素上方的顶层“打开”,然后在“关闭”时将其从顶层删除。 我非常有信心,我们所有人都遇到过这样的情况:我们打开了一个模式,但无论出于何种原因,它都没有出现。并不是它没有正确打开,而是它在堆叠上下文的较低层中看不见。 这让你想知道“怎么会这样?”自从你设置:
.覆盖{ 位置:固定; /* 创建堆栈上下文 */ z 索引:1; /* 将元素放在其他所有元素之上的层上 */ 插图:0; 宽度:100%; 高度:100vh; 溢出:隐藏; 背景颜色:#00000080; }
这看起来是正确的,但如果包含模态触发器的父元素是另一个也设置为 z-index: 1 的父元素中的子元素,从技术上讲,这会将模态放置在被主文件夹遮挡的子图层中。让我们看看这个特定场景和其他几个常见的堆栈上下文陷阱。我想您不仅会看到无意中创建堆叠上下文是多么容易,还会看到如何错误地管理它们。此外,如何返回托管状态取决于具体情况。 场景 1:受困模态框
您可以立即看到困在低层中的模态并识别父模态。 浏览器扩展 聪明的开发人员已经构建了扩展来提供帮助。像“CSS Stacking Context Inspector”这样的工具 Chrome 扩展程序会在您的 DevTools 中添加一个额外的 z-index 选项卡,以向您显示有关创建堆栈上下文的元素的信息。
IDE 扩展 您甚至可以使用 VS Code 之类的扩展来发现开发过程中的问题,该扩展可以直接在编辑器中突出显示潜在的堆栈上下文问题。
解除堆叠并重新获得控制 确定根本原因后,下一步就是处理它。您可以采取多种方法来解决这个问题,我将按顺序列出它们。不过,你可以选择任何级别的任何人;任何人都不能抱怨或阻碍他人。 更改 HTML 结构 这被认为是最佳修复。为了让您遇到堆叠上下文问题,您必须将一些元素放置在 HTML 中有趣的位置。重构页面将帮助您重塑 DOM 并消除堆叠上下文问题。找到有问题的元素并将其从 HTML 标记中的捕获元素中删除。例如,我们可以通过将 .modal-container 从标头中移出并将其单独放置在
元素中来解决第一个场景“陷阱模态”。<标题类=“标题”>
标题
标题> <主类=“内容”>主要内容
此内容的 z 索引为 2,但仍不会覆盖模态框。
主要>
当您单击“打开模态”按钮时,模态将按预期放置在其他所有内容的前面。 请参阅 Shoyombo Gabriel Ayomide 的钢笔场景 1:受困模态(解决方案)[forked]。 调整CSS 中的父堆栈上下文 如果您无法在不破坏布局的情况下移动该元素怎么办?最好解决这个问题:父母建立上下文。找到负责触发上下文的 CSS 属性(或多个属性)并将其删除。如果它有目的且无法删除,请为父级赋予比其兄弟元素更高的 z-index 值,以提升整个容器。 z-index 值较高时,父容器会移至顶部,并且其子容器会显得更靠近用户。 根据我们在“淹没的下拉菜单”场景中学到的知识,我们无法将下拉菜单移出导航栏;这没有意义。但是,我们可以将 .navbar 容器的 z-index 值增加到大于 .content 元素的 z-index 值。 .导航栏{ 背景:#333; /* z 索引: 1; */ z 索引:3; 位置:相对; }
通过此更改,.dropdown-menu 现在显示在内容前面,没有任何问题。 请参阅 Shoyombo Gabriel Ayomide 的钢笔场景 2:淹没下拉菜单(解决方案)[forked]。 如果使用框架,请尝试门户 在 React 或 Vue 等框架中,Portal 是一项功能,可让您在 DOM 中正常父层次结构之外渲染组件。门户就像组件的传送设备。它们允许您在文档中的任何位置渲染组件的 HTML(通常在 document.body 中),同时保持其与原始父级的 props、state 和 events 的逻辑连接。这非常适合逃避堆叠上下文陷阱,因为渲染的输出实际上出现在有问题的父容器之外。 ReactDOM.createPortal( <工具提示/>, 文档正文 );
这可以确保您的下拉内容不会隐藏在其父级后面,即使父级具有溢出:隐藏或较低的 z-index 也是如此。 在我们之前看到的“剪切工具提示”场景中,我使用 Portal 将工具提示从溢出:隐藏剪辑中拯救出来,方法是将其放置在文档正文中并将其放置在容器内触发器上方。 请参阅 Shoyombo Gabriel Ayomide 的钢笔场景 3:剪辑的工具提示(解决方案)[forked]。 引入无副作用的堆栈上下文 上一节中解释的所有方法都旨在从有问题的堆叠上下文中“拆开”元素,但在某些情况下您实际上需要或想要创建堆叠上下文。 创建新的堆栈上下文很容易,但所有方法都会产生副作用。即,除了使用隔离:isolate。当应用于元素时,该元素的子元素的堆叠上下文是相对于每个子元素并在该上下文内确定的,而不是受到其外部元素的影响。一个典型的例子是为该元素分配负值,例如 z-index: -1。 假设您有一个 .card 组件。您想要添加位于 .card 文本后面、卡片背景之上的装饰形状。如果卡片上没有堆叠上下文,z-index: -1 会将形状发送到根堆叠上下文(整个页面)的底部。这使得它消失在 .card 的白色背景后面: 请参阅 Shoyombo Gabriel Ayomide 的 Pen Negative z-index(问题)[forked]。 为了解决这个问题,我们在父 .card 上声明隔离:isolate: 请参阅 Shoyombo Gabriel Ayomide 的 Pen Negative z-index(解决方案)[forked]。 现在,.card 元素本身成为堆叠上下文。当它的子元素(在 :before 伪元素上创建的装饰形状)的 z-index: -1 时,它会转到父级堆叠上下文的最底部。正如预期的那样,它完美地位于文本后面和卡片背景的顶部。 结论 请记住:下次当你的 z-index 看起来失控时,它就是一个被困的堆叠上下文。 参考文献
堆栈上下文 (MDN) Z-index 和堆叠上下文 (web.dev) “如何使用 CSS 中的隔离属性创建新的堆叠上下文”,Natalie Pina “到底是什么,z-index?”,乔什·科莫
进一步阅读 SmashingMag
“在大型项目中管理 CSS Z-Index”,Steven Frieson “粘性标题和全高元素:一个棘手的组合”,Philip Braunen “在基于组件的 Web 应用程序中管理 Z-Index”,Pavel Pomerantsev “Z-Index CSS 属性:全面了解”,Louis Lazaris