z-index 用来控制元素在 Z 轴上的叠放顺序,也就是多个元素重叠时,谁显示在上面。
可以把页面理解成三维空间:
X 轴:左右
Y 轴:上下
Z 轴:前后、遮挡关系
例如:
css
.first {
z-index: 1;
}
.second {
z-index: 2;
}
当两个元素重叠时,second 通常显示在 first 上面。
基本用法
ini
<div class="box first">第一层</div>
<div class="box second">第二层</div>
css
.box {
position: absolute;
width: 100px;
height: 100px;
}
.first {
left: 0;
top: 0;
background: red;
z-index: 1;
}
.second {
left: 50px;
top: 50px;
background: blue;
z-index: 2;
}
结果:
css
蓝色元素的 z-index 更大
所以蓝色元素覆盖红色元素
UnoCSS 写法:
ini
<div class="absolute z-10">第一层</div>
<div class="absolute z-20">第二层</div>
任意值:
css
<div class="absolute z-[999]">弹窗</div>
z-index 没有单位
正确:
css
z-index: 10;
错误:
css
z-index: 10px;
z-index 表示层级数字,不是长度,所以不能加 px。
z-index 通常需要配合定位
传统普通元素要让 z-index 生效,通常需要设置:
css
position: relative;
position: absolute;
position: fixed;
position: sticky;
例如:
css
.box {
position: relative;
z-index: 10;
}
UnoCSS:
ini
<div class="relative z-10">
内容
</div>
下面这种写法可能不生效:
css
.box {
position: static;
z-index: 10;
}
因为 static 是默认定位方式。
不过 Flex 和 Grid 的直接子元素比较特殊,即使没有定位,z-index 也可以生效:
css
.parent {
display: flex;
}
.child {
z-index: 10;
}
数字越大不一定永远越靠上
很多人会以为:
css
z-index: 999999;
一定能覆盖:
css
z-index: 10;
实际上不一定,因为 z-index 只能在当前层叠上下文中比较。
什么是层叠上下文
层叠上下文可以理解为一个独立的层级分组。
例如:
css
<div class="parent-a">
<div class="child-a">A 的子元素</div>
</div>
<div class="parent-b">
B 元素
</div>
css
.parent-a {
position: relative;
z-index: 1;
}
.child-a {
position: absolute;
z-index: 999999;
}
.parent-b {
position: relative;
z-index: 2;
}
虽然:
css
.child-a {
z-index: 999999;
}
但它仍然可能被 parent-b 覆盖。
原因是:
css
parent-a 整个分组的层级是 1
parent-b 整个分组的层级是 2
浏览器先比较父级分组:
css
parent-b 的 2 > parent-a 的 1
因此 parent-b 整体在上面。
child-a 的 999999 只能在 parent-a 内部比较,无法越过父级层叠上下文。
可以理解为:
css
页面层叠上下文
├── parent-a:z-index 1
│ └── child-a:z-index 999999
│
└── parent-b:z-index 2
比较顺序是:
css
先比较 parent-a 和 parent-b
再比较它们各自内部的子元素
哪些属性会创建层叠上下文
常见情况包括:
css
/* 定位元素并且 z-index 不是 auto */
position: relative;
z-index: 1;
css
position: absolute;
z-index: 1;
以及:
css
position: fixed;
position: sticky;
opacity: 0.9;
transform: translateX(0);
filter: blur(0);
perspective: 1000px;
isolation: isolate;
will-change: transform;
Flex 或 Grid 子元素设置非 auto 的 z-index,也可能创建层叠上下文。
例如:
css
.parent {
transform: translateX(0);
}
即使没有设置 z-index,transform 也可能让它形成新的层叠上下文。
这也是很多"z-index: 9999 为什么不生效"问题的来源。
z-index: auto 和 z-index: 0
默认值是:
css
z-index: auto;
auto 表示让浏览器按照默认层叠规则处理,而且通常不会因为这个属性本身创建新的层叠上下文。
css
z-index: 0;
表示明确把元素放到当前上下文的第 0 层。对于定位元素、Flex 项目或 Grid 项目,它还可能创建新的层叠上下文。
因此:
css
z-index: auto;
和:
css
z-index: 0;
并不完全相同。
z-index 相同时怎么办
如果两个元素处于同一个层叠上下文,并且 z-index 相同,通常后面出现的元素显示在上面。
ini
<div class="first">第一个</div>
<div class="second">第二个</div>
css
.first,
.second {
position: absolute;
z-index: 10;
}
因为 second 在 DOM 中更靠后,所以通常会覆盖 first。
更推荐明确设置不同层级:
css
.first {
z-index: 10;
}
.second {
z-index: 20;
}
负数 z-index
可以设置负数:
css
z-index: -1;
表示把元素放到更后面。
但是负数层级容易导致元素跑到父元素背景后面,甚至完全看不见:
css
.parent {
position: relative;
background: white;
}
.child {
position: absolute;
z-index: -1;
}
因此项目中应谨慎使用负数 z-index。
overflow 会影响显示
假设父元素设置:
css
.parent {
overflow: hidden;
}
即使子元素设置:
css
.child {
z-index: 999999;
}
超出父元素范围的部分仍然会被裁剪。
这是因为:
css
z-index 控制叠放顺序
overflow 控制裁剪范围
z-index 无法突破父元素的 overflow: hidden。
z-index 不生效时如何排查
1. 检查是否有定位
css
position: relative;
z-index: 10;
2. 检查祖先元素
祖先元素是否设置了:
css
transform
opacity
filter
z-index
isolation
will-change
它们可能创建新的层叠上下文。
3. 比较父级层叠上下文
不要只比较当前两个元素的 z-index,还要比较它们祖先元素的层级。
4. 检查 overflow
css
overflow: hidden;
overflow: clip;
可能把元素裁剪掉。
5. 检查元素是否真正重叠
如果两个元素没有空间重叠,改变 z-index 不会产生明显效果。
推荐的层级管理
不要到处写:
css
z-index: 99999;
z-index: 99999999;
最好给项目定义固定层级:
yaml
普通内容 0
固定导航 10
下拉菜单 100
遮罩层 900
弹窗 1000
消息提示 1100
例如 UnoCSS:
ini
<div className="relative z-0">普通内容</div>
<header className="sticky top-0 z-10">导航栏</header>
<div className="absolute z-100">下拉菜单</div>
<div className="fixed inset-0 z-900">遮罩层</div>
<div className="fixed z-1000">弹窗</div>
最后可以这样记忆:
css
z-index 控制的是同一个层叠上下文中的前后顺序
而不是:
数字越大,就一定能覆盖页面中的所有元素
另外,层叠上下文和 GPU 合成层不是同一个概念:
层叠上下文:决定谁盖住谁
合成层:浏览器的渲染性能优化