一、引子
见下图,我们有 one
、two
、three
三个 div
。
one
嵌套了一个 nested
的 div
。
nested
的 z-index
等于 100,two
的 z-index
只等于 1。
但是 nested
却仍然被 two
覆盖。
Why?
别着急,我们慢慢剖析。
二、正常的元素顺序
我们新建三个div
:
<div>one</div>
<div>two</div>
<div>three</div>
页面效果展示如下(详细代码见附录 A):
正常的文档流中,元素次序是跟元素在文档中出现次序一致。
three
出现最后,次序最高,覆盖掉 two
。
以此类推,two
出现比 one
稍晚,所以覆盖住 one
。
三、positioned元素脱颖而出
当我们为 two
加上 position:relative
后(代码见附录B):
two
脱颖而出,覆盖住了 one
和 three。
这是因为,浏览器先绘制 non-positioned 元素,再绘制 positioned 元素。
non-positioned元素 指
position
属性值为默认值static
,那么不为static
的就是 positioned元素 。
四、都是positioned元素呢?
把 three
也设置为 position: relative
的话(详细代码见附录 C):
变回老样子,positioned 元素次序和在文档出现次序一致。
想要让 two
再次脱颖而出,那考虑 z-index
吧!
五、z-index
的用处
当把 two
设置为 z-index: 1
后(详细代码见附录 D):
通过 z-index
让 two
排到了前面。
“Z”代表 X-Y-Z 笛卡尔坐标系中的深度。
z-index
数值高的元素会排在 z-index
数字较低的元素之前。
使用 z-index
要注意两个陷阱:
1. z-index
只应用于 positioned元素
2. 隐式地创建一个 层叠上下文(stack context)。
🚀 注意, 层叠上下文和 BFC(Block Formatting Context)有所区别:
层叠上下文解决元素次序问题(一个元素是否排在另一个元素之前),而 BFC 解决文档流和一个元素是否会被重叠。
六、 层叠上下文
当你在一个 positioned 元素上应用 z-index
,会隐式创建一个全新的 层叠上下文,这个元素同时成为了该 层叠上下文的根元素。
上节的 two
就是一个层叠上下文的根元素。
z-index
只控制当前层叠上下文的元素次序。
我们把 one
和 two
设为position: relative
,并且 z-index
都为 1
。
并且在 one
中内嵌一个 nested
的 div
,nested
设置(具体代码见附录E):
position: absolute;
z-index: 100;
结果展示的就是我们开头所提到的:
虽然 nested 的 z-index
为 100,可是还是被 two
所覆盖。
这是因为,nested
和 two
分别属于两个不同的层叠上下文。
z-index
只控制自己当前层叠上下文的元素次序。
七、 深入层叠上下文
除了 z-index
会创建层叠上下文,opacity
的值低于 1 也会创建,还有 transform
和 filter
也会,全部请详见附录F。
层叠上下文中元素的次序按照以下规则:
1. 层叠上下文的根元素
⬇️
2. z-index
为负数的 positioned 元素
⬇️
3. Non-positioned 元素
⬇️
4. z-index
为 auto
的 positioned 元素
⬇️
5. z-index
为正数的 positioned 元素
非必要不创建层叠上下文 ⚠️
在上一节的例子中,我们就感受到了,多个层叠上下文,一方面会造成混乱,另一方面会使我们受挫,达不到想要的效果。
八、 消除z-index
魔术字的技巧
魔术字(magic number)指使用直接的数字字面量,这个值背后所代表的意思是隐藏着的,就像魔术一样。
在之前的例子中,z-index
为 1,或者为 100,都可以算魔术字。
消除魔术字,最好的方法是用变量规范起来。
比如,我们用变量去规范程序中各个 z-index
的值:
--z-loading-indicator: 100;
--z-nav-menu: 200;
--z-dropdown-menu: 300;
--z-modal-backdrop: 400;
--z-modal-body: 410;
附录A
HTML代码
<body>
<div class="box one">one</div>
<div class="box two">two</div>
<div class="box three">three</div>
</body>
CSS代码
body {
margin: 40px;
}
.box {
display: inline-block;
width: 200px;
line-height: 200px;
text-align: center;
border: 2px solid black;
background-color: #ea5;
margin-left: -60px;
vertical-align: top;
}
.one {
margin-left: 0;
}
.two {
margin-top: 30px;
}
.three {
margin-top: 60px;
}
附录B
HTML代码
<body>
<div class="box one">one</div>
<!-- 增加一个class名 -->
<div class="box two positioned">two</div>
<div class="box three">three</div>
</body>
CSS代码
body {
margin: 40px;
}
.box {
display: inline-block;
width: 200px;
line-height: 200px;
text-align: center;
border: 2px solid black;
background-color: #ea5;
margin-left: -60px;
vertical-align: top;
}
.one {
margin-left: 0;
}
.two {
margin-top: 30px;
}
.three {
margin-top: 60px;
}
/* 增加设置position值 */
.positioned {
position: relative;
background-color: #5ae;
}
附录C
HTML代码
<body>
<div class="box one">one</div>
<div class="box two positioned">two</div>
<!-- three也设置为positioned -->
<div class="box three positioned">three</div>
</body>
CSS代码
body {
margin: 40px;
}
.box {
display: inline-block;
width: 200px;
line-height: 200px;
text-align: center;
border: 2px solid black;
background-color: #ea5;
margin-left: -60px;
vertical-align: top;
}
.one {
margin-left: 0;
}
.two {
margin-top: 30px;
}
.three {
margin-top: 60px;
}
/* CSS其实不变 */
.positioned {
position: relative;
background-color: #5ae;
}
附录D
HTML代码
<body>
<div class="box one">one</div>
<div class="box two positioned">two</div>
<div class="box three positioned">three</div>
</body>
CSS代码
body {
margin: 40px;
}
.box {
display: inline-block;
width: 200px;
line-height: 200px;
text-align: center;
border: 2px solid black;
background-color: #ea5;
margin-left: -60px;
vertical-align: top;
}
.one {
margin-left: 0;
}
.two {
margin-top: 30px;
}
.three {
margin-top: 60px;
}
.positioned {
position: relative;
background-color: #5ae;
}
/* 设置z-index */
.two{
z-index: 1;
}
附录E
HTML代码
<body>
<div class="box one positioned">
one
<div class="absolute">nested</div>
</div>
<div class="box two positioned">two</div>
<div class="box three">three</div>
</body>
CSS代码
body {
margin: 40px;
}
.box {
display: inline-block;
width: 200px;
line-height: 200px;
text-align: center;
border: 2px solid black;
background-color: #ea5;
margin-left: -60px;
vertical-align: top;
}
.one {
margin-left: 0;
}
.two {
margin-top: 30px;
}
.three {
margin-top: 60px;
}
.positioned {
position: relative;
background-color: #5ae;
z-index: 1;
}
.absolute {
position: absolute;
top: 1em;
right: 1em;
height: 2em;
background-color: #fff;
border: 2px dashed #888;
padding: 1em;
line-height: initial;
z-index: 100;
}
附录F
满足以下任意条件可创建层叠上下文[2]:
- 文档根元素(
<html>
); position
属性值为absolute
或relative
,并且z-index
不为auto
的元素;position
属性值为fixed
或sticky
的元素flex
容器的子元素,且z-index
值不为auto
grid
容器的子元素,且z-index
值不为auto
opacity
属性值小于1
的元素mix-blend-mode
属性值不为normal
的元素;- 以下任意属性值不为
none
的元素transform
filter
perspective
- clip-path
mask
/ mask-image /mask-border
isolation
属性值为isolate
的元素-webkit-overflow-scrolling
属性值为touch
的元素- 值设定了任一属性而该属性在
non-initial
值时会创建层叠上下文的元素(参考这篇文章[3]) contain
属性值为layout
、paint
或包含它们其中之一的合成值,比如(contain: strict
、contain: content
)的元素
Reference
[1] CSS In Depth: 7.4 Stacking contexts and z-index
[2] 层叠上下文: https://developer.mozilla.org/zh-CN/docs/Web/CSS/CSS_Positioning/Understanding_z_index/The_stacking_context
[3] Everything You Need to Know About the CSS will-change Property: https://dev.opera.com/articles/css-will-change-property/