先记住这个答案
不写 z-index 时,浏览器在同一个层叠上下文里按固定的层来画,从下到上依次是:普通块级元素的背景和边框、浮动元素、行内内容(文字、图片、inline-block)、定位元素。先比较两个元素分别在哪一层,层高的盖住层低的;在同一层里再看 DOM 顺序,后出现的盖住先出现的。所以「后来者居上」只在同类元素之间成立,一个 position: relative 的元素即使写在前面,也会盖住后面的普通元素。
- 先比层级:块背景 < 浮动 < 行内内容 < 定位元素
- 同一层里按 DOM 顺序,后出现的在上面
- 定位元素不写
z-index也压在普通元素上面 - 后一个块的背景盖不住前一个块的文字
定位元素一定在普通元素上面
最常见的场景是用负外边距让两个元素重叠,然后发现写在前面的元素反而在上面。
<style>
.a { position: relative; background: tomato; height: 60px; }
.b { background: skyblue; height: 60px; margin-top: -30px; }
</style>
<div class="a">A:relative,没写 z-index</div>
<div class="b">B:普通块,DOM 在后面</div>
<!-- 重叠的 30px 里看到的是红色的 A,B 的蓝色背景被压在下面 -->B 在 DOM 里排在后面,按直觉应该盖住 A。但 A 是定位元素,它属于最上面那一层,B 只是普通块,属于最下面那一层,所以 A 赢。把 A 的 position: relative 去掉,两个都变成普通块,B 就会盖住 A。
这就是 UI 里常见的「明明没写 z-index,某个元素却浮在上面」的原因,往往是有人为了做绝对定位的子元素,给它加了 position: relative。
反直觉的一幕:背景盖不住文字
两个都是普通块时,也不是简单地「后一个整块盖住前一个」。浏览器会先把所有普通块的背景画完,再统一画所有文字。
- 前一个块的文字浮在后一个块的背景上
- 还是上面那个例子,去掉 A 的定位,让 A 有文字、B 用负外边距往上压。重叠区域里,B 的蓝色背景确实盖住了 A 的红色背景,但 A 的文字依然清清楚楚地显示在蓝色背景上面。因为 A 的文字属于「行内内容」这一层,而 B 的背景属于更低的「块级背景」层。 浮动元素夹在这两层中间:它会盖住普通块的背景,但普通块里的文字会绕开它排布;如果文字真的和浮动重叠,文字画在浮动上面。
完整的分层顺序
上面讲的是面试最常考的部分。完整的顺序由规范定义,一个层叠上下文内部从下到上是:
- 七层从下到上
- 第一层是层叠上下文根元素自己的背景和边框。第二层是
z-index为负数的子层叠上下文。第三层是普通流里块级后代的背景和边框。第四层是非定位的浮动元素。第五层是普通流里的行内内容。第六层是z-index: auto或z-index: 0的定位元素,以及opacity小于 1、有transform这类不靠定位就创建了层叠上下文的元素,它们之间按 DOM 顺序。第七层是z-index为正数的元素,数值小的在下。 题目问的「都没写z-index」,对应的就是第三到第六层。记住这四层的先后,再加上同层按 DOM 顺序,基本就能判断所有情况。
容易答错的地方
- 以为 DOM 顺序就是唯一标准
- DOM 顺序只在同一层里起作用。一个普通元素写得再靠后,也盖不住前面的定位元素。回答时先说分层,再说 DOM 顺序。
- 以为给元素加
opacity不影响层叠 opacity: 0.99会创建层叠上下文,元素被提到和定位元素同一层,DOM 顺序靠后时就会盖住前面的定位元素。有时候加了个淡入动画,元素突然跑到弹层上面,就是这个原因。transform、filter也一样。- 以为
z-index写在普通元素上也有用 - 没有定位的块级元素写
z-index不生效,它还在原来的层里。例外是 flex 和 grid 的子项,它们不用定位也能让z-index生效。
面试官还会怎么问?
两个定位元素都写了 z-index: 1,谁在上面?
数值相同就回到 DOM 顺序,后出现的在上面。前提是它们处在同一个层叠上下文里;如果分属两个父层叠上下文,要比较的是两个父级。
z-index: auto 和 z-index: 0 看起来一样,有什么区别?
两者都画在第六层,和兄弟元素的相对位置一样。区别是 z-index: 0 会创建新的层叠上下文,它的子元素写多大的 z-index 都出不去;auto 不创建,子元素还在外面那个上下文里参与比较。
怎么让一个普通元素盖住前面的定位元素,又不想引入 z-index?
给它加 position: relative,让它和前面的定位元素处在同一层,这时比较 DOM 顺序,它在后面就会赢。或者调整 DOM 顺序。实际项目里更推荐显式写 z-index,并用 isolation: isolate 把组件的层级关在组件内部。
参考资料
示例用于理解所注明的运行环境与边界;延伸学习可结合原文中的更多案例。