我们在编写 CSS 样式时不少遇到这样的疑惑:“为什么这个元素会被覆盖?”、“为什么设置较高的 z-index
值还是不起效果?”
不妨先想想这个问题,当多个 HTML 元素在浏览器视窗中发生重叠时,浏览器会怎么安排哪个元素显示在上、哪个显示在下呢?其实所有元素在发生层叠时的表现都是按照一定的优先级顺序的,这些顺序规则都是建立在 层叠上下文(The Stacking Context) 这个三维概念中,我们一起来了解下。
层叠上下文特性
-
在同一个层叠上下文中,子元素按照 层叠顺序 规则进行层叠;
<style> .wrapper { position: relative; z-index: 1; } .wrapper div:nth-of-type(1) { position: relative; z-index: -1; } .wrapper div:nth-of-type(2) { display: block; } .wrapper div:nth-of-type(3) { float: left; } .wrapper div:nth-of-type(4) { display: inline-block; } .wrapper div:nth-of-type(5) { position: relative; } .wrapper div:nth-of-type(6) { position: absolute; z-index: 1; } </style> <body> <div class="wrapper"> <div>position: relative;<br>z-index: -1;</div> <div>display: block;</div> <div>float: left;</div> <div>display: inline-block;</div> <div>position: relative;</div> <div>position: absolute;<br>z-index: 1;</div> </div> </body>
关于 层叠顺序 的详解请往下看,完整代码已上传 CodePen:https://codepen.io/JunreyCen/pen/WqoLmr
另外,当不同层叠顺序的元素相比较时,不关心元素在 DOM 树中的结构关系:
<style> .child { position: relative; z-index: -1; } </style> <div class="parent"> <div class="child"></div> </div> <div class="parent" style="opacity: 0.6;"> <div class="child"></div> </div>
左边实例中
Parent
元素不是层叠上下文元素,所以和Child
元素是处于同一个层叠上下文中,而根据层叠顺序,普通流块级元素 是叠在 负z-index
定位元素 之上的。右边实例中Parent
元素指定属性opacity: 0.6
,创建了一个层叠上下文,从而使Child
元素包含在内,根据层叠顺序 负z-index
定位元素 会叠在 根元素背景和边框 之上。这样的表现说明,Parent
和Child
元素的父子节点关系不会影响层叠关系。 -
在同一个层叠上下文中,当元素的层叠顺序相同时,按照 元素在 HTML 中出现的顺序 进行层叠;
<style> .box-1 { position: relative; } .box-2 { position: absolute; top: 50px; left: 50px; } </style> <div class="wrapper"> <div class="box-1"></div> <div class="box-2"></div> </div> <div class="wrapper"> <div class="box-2"></div> <div class="box-1"></div> </div>
所有
z-index
属性值为0
/auto
的定位元素的层叠顺序都是相同的,如实例中的box-1
和box-2
元素,可以看出它们发生层叠时遵循的是 在 HTML 中出现的先后顺序。 层叠上下文支持嵌套,子级层叠上下文对于父级层叠上下文是一个独立单元,对于兄弟元素来说也是相互独立的;
-
每个层叠上下文都是自包含的,无论在子级层叠上下文内如何 “翻云覆雨”,也是基于父级层叠上下文的层叠顺序下;
<style> .parent-1, .parent-2, .child-1, .child-2 { position: relative; } .parent-1 { z-index: 2; } .parent-2 { z-index: 1; } .child-1 { z-index: -1; } .child-2 { z-index: 10; } </style> <div class="parent-1"> <div class="child-1"></div> </div> <div class="parent-2"> <div class="child-2"></div> </div>
Child-1
和Child-2
元素分别处于各自的父级层叠上下文元素Parent-1
和Parent-2
中。由于Parent-1
是层叠于Parent-2
之上的,即使Child-1
的z-index: -1
属性值小于Child-2
的z-index: 10
,Child-2
也不能逾越其父级上下文覆盖在Child-1
上面的。换句话说,子级层叠上下文的层级讨论只在其父级层叠上下文中有意义。
创建层叠上下文
满足以下 12 个条件中任意一个的元素会创建一个层叠上下文(摘自 MDN ):
1、根元素 <html>
;
2、z-index
值不为 auto
的相对定位(position: relative
)和绝对定位(position: absolute
);
3、固定定位(position:fixed
);
4、z-index
值不为 auto
的伸缩盒模型中的 flex 项目;
5、opacity
属性值小于 1
;
6、transform
属性值不为 none
;
7、filter
属性值不为 none
;
8、mix-blend-mode 属性值不为 normal
;
9、perspective 属性值不为 none
;
10、isolation 属性值为 isolate
;
11、will-change 属性 指定了上述任意一个 CSS 属性(即便没有直接指定这些属性的值);
12、-webkit-overflow-scrolling 属性值为 touch
;
关于第 11 点中的
will-change
属性:该属性主要用于告知浏览器元素会发生哪些变化,有利于浏览器在元素真正变化之前提前做好优化准备。所以当will-change
指定了能创建层叠上下文的 CSS 属性(包括z-index
属性)时,浏览器也会为该元素创建层叠上下文。
层叠顺序
层叠顺序就是元素在层叠上下文中的显示规则。在此之前需要提前认识一个重要概念:z-index
只会对定位元素(非普通流)有效 !
当元素发生层叠时,会按照下面的 7 阶层叠顺序来决定元素显示的前后顺序:
1、根元素的背景与边框;
2、z-index < 0
的定位元素;
3、普通流中的块元素(display: block
);
4、浮动块元素;
5、普通流中的行内元素(display: inline
/ display: inline-block
);
6、z-index
属性值为 0
/ auto
的定位元素、其他子级层叠上下文元素;
7、z-index > 0
的定位元素;
- 普通流:不指定
position
或者position: static
;- 上面第 6 点中的 其他 子级层叠上下文元素:不依赖
z-index
属性就能创建层叠上下文的元素;
注意事项
-
层叠顺序的第 6 阶中,
z-index
属性值为0
/auto
的定位元素 和 不依赖z-index
的子级层叠上下文元素 顺序相同,会遵循 在 HTML 中出现的先后顺序 来层叠;<div class="wrapper"> <div class="box-1" style="opacity: 0.8;"></div> <div class="box-2" style="position: absolute;"></div> </div> <div class="wrapper"> <div class="box-2" style="position: absolute;"></div> <div class="box-1" style="opacity: 0.8;"></div> </div>
-
伸缩盒没有创建层叠上下文,伸缩盒中
z-index
值不为auto
的 flex 项目才会创建层叠上下文。换句话说,使用这种方式创建层叠上下文需要同时满足两个条件:- 父元素设置了
display: flex
/display: inline-flex
; - 自身
z-index
属性值不为auto
;
<style> .child { position: relative; z-index: -1; } </style> <div class="container"> <div class="item"> <div class="child"></div> </div> </div> <div class="container" style="display: inline-flex;"> <div class="item" style="z-index: 1;"> <div class="child"></div> </div> </div> <div class="container" style="display: inline-flex;"> <div class="item" style="z-index: -1;"> <div class="child"></div> </div> </div>
左边实例,
Container
、Item
元素都没有创建层叠上下文,这两者与Child
元素都处于同一个层叠上下文中,按照层叠顺序渲染;中间实例,
Container
元素设置display: inline-flex
成为 Flex 容器,Item
元素设置z-index: 1
,则Item
元素创建了层叠上下文,所以即使Child
元素的z-index
值为负数,却依然层叠在Item
元素之上;右边实例,把
Item
元素的z-index
值改为-1
,由于Container
元素作为 Flex 容器是没有创建层叠上下文的,所以Item
元素会层叠在Container
元素之下。 - 父元素设置了
固定定位(position: fixed
)的特殊性
我们发现,单纯是绝对/相对定位元素是无法创建一个层叠上下文的,需要同时满足 z-index
值不为 auto
的条件。然而,固定定位元素就不需要满足这个条件,显得尤为特殊。
-
固定定位元素无需满足
z-index
值不为auto
的条件就可以创建层叠上下文;同样地,设置z-index: auto
并不能撤销固定定位元素所创建的层叠上下文。<style> .fixed { position: fixed; z-index: auto; } .child { position: relative; z-index: -1; } </style> <div class="fixed"> <div class="child"></div> </div>
-
正常情况下,固定定位是相对于浏览器视窗(
viewport
)进行定位的,但是当其祖先元素中存在符合以下任意一个条件的元素时,固定定位元素会相对于该元素进行定位:1、
transform
属性值不为 none;
2、transform-style: preserve-3d
;
3、perspective
属性值不为none
;
4、will-change
属性 指定了上面 3 个 CSS 属性中的任意一个;然而,这种表现在不同的浏览器中有差异,譬如在 Safari 中只有上述第 1 点会对固定定位元素产生影响。
建议
通过对层叠上下文和层叠顺序的了解,我们知道,要控制元素间的层叠关系除了使用 z-index
属性外还有很多途径,而且比使用 z-index
要优雅得多。滥用 z-index
往往只会把层叠关系复杂化,造成代码难以维护。
友情链接
学习完 CSS 层叠上下文,如果对 BFC(块格式化上下文)、IFC(行内格式化上下文)等相关概念有兴趣的,可以学习这篇文章:视觉格式化模型 | MDN。
文中涉及到的完整实例代码已放在 Github 上,欢迎大家交流。