对于堆叠上下文,你可能没有听说过这个术语,但是你肯定有接触过它。当你在使用绝对定位和z-index来控制元素的层级时,该元素就建立了自己的堆叠上下文。
所以堆叠上下文是比较经常能接触到的,了解相关的知识也是很有必要的。
概念
堆叠上下文
堆叠上下文是HTML元素的三维概念,这些HTML元素在一条假想的相对于面向(电脑屏幕的)视窗或者网页的用户的 z 轴上延伸,HTML 元素依据其自身属性按照优先级顺序占用层叠上下文的空间。
简单来说,为了让HTML元素在网页上绘制时有个前后的顺序(也就是z轴的概念),这时就要产生堆叠上下文来进行比较元素在z轴的位置。
那么如何触发元素的堆叠上下文呢?
- 根堆叠上下文(我们所有的元素排序都是在此上下文中进行的)
- z-index值为数值的定位元素的传统堆叠上下文
- CSS属性引起的
- z-index值不为auto的flex项(父元素display:flex|inline-flex)
- 元素的opacity值不是1
- 元素的transform值不是none
- 元素mix-blend-mode值不是normal
- 元素的filter值不是none
- 元素的isolation值是isolate
- will-change指定的属性值为上面任意一个
- 元素的-webkit-overflow-scrolling设为touch
堆叠水平和堆叠顺序
堆叠水平决定了同一个堆叠上下文中元素在z轴上的显示顺序;所有的元素都有堆叠水平,包括堆叠上下文元素。
堆叠顺序表示元素发生堆叠时候有着特定的垂直显示顺序。堆叠上下文和堆叠水平是概念,堆叠顺序是规则。
这边借用一张图来说明下堆叠顺序:
除了上面的堆叠顺序,还有两个堆叠的准则:
- 谁大谁上:如识别的z-indx值越大越高。
- 后来居上:堆叠水平一致、堆叠顺序相同时。
实例
堆叠上下文有着自己的特性,接下来结合实例来说明。
- 堆叠上下文的堆叠水平要比普通元素高
<style>
.block-1 {
height: 160px;
width: 200px;
background-color: #00BCD4;
/* transform: scale(1); */
}
.block-2 {
height: 160px;
width: 200px;
margin-top: -100px;
background-color: #FFC107;
}
</style>
<div class="block-1"></div>
<div class="block-2"></div>
正常情况下是后来居上,但是使用transfrom属性在block-1触发堆叠上下文后,block-1会显示在前面来。
- 堆叠上下文可以嵌套,内部堆叠上下文及其所有子元素均受制于外部的堆叠上下文
<style>
.block-1 {
height: 160px;
width: 200px;
background-color: #00BCD4;
/* transform: scale(1); */
}
.block-1-inner {
position: relative;
display: inline-block;
height: 60px;
width: 60px;
margin-bottom: -80px;
z-index: 10;
background-color: #F44336;
}
.block-2 {
position: absolute;
height: 160px;
width: 200px;
margin-top: -100px;
background-color: #FFC107;
z-index: 5;
}
</style>
<div class="block-1">
<div class="block-1-inner">
</div>
<div class="block-2"></div>
可以看到在block-1没有产生堆叠上下文时,红色小块是处理最高层的,但block-1产生用transform产生堆叠上下文时,红色小块会受制于block-1,处于block-2下层。
- 每个堆叠上下文和兄弟元素独立,也就是当进行堆叠变化或渲染的时候,只需要考虑后代元素的影响
这个比较好理解,元素自己产生堆叠上下文后,不影响兄弟节点,只影响后代节点。
- 每个堆叠上下文是自成体系的,当元素发生堆叠的时候,整个元素被认为是在父堆叠上下文的堆叠顺序中
这点在第二个例子中也能得到验证,内部红色小块在block-1有堆叠上下文情况下,它是处于block-1内部堆叠顺序中。
合成层问题
堆叠上下文有时会引起合成层爆炸的问题。可以看下这个例子
<style>
@-webkit-keyframes slide {
from {
ransform: none;
}
to {
transform: translateX(100px);
}
}
.animating {
width: 300px;
height: 30px;
background-color: orange;
color: #fff;
-webkit-animation: slide 5s alternate linear infinite;
/* position: relative; */
/* z-index: 1; */
}
ul {
padding: 5px;
border: 1px solid #000;
}
.box {
width: 600px;
height: 30px;
margin-bottom: 5px;
background-color: blue;
color: #fff;
position: relative;
/* 会导致无法压缩:squashingClippingContainerMismatch */
overflow: hidden;
/* 当渲染层同合成层有不同的裁剪容器(clipping container)时,该渲染层无法压缩(squashingClippingContainerMismatch) */
}
.inner {
position: absolute;
top: 2px;
left: 2px;
font-size: 16px;
line-height: 16px;
padding: 2px;
margin: 0;
background-color: green;
}
</style>
<body>
<div class="animating">composited animating</div>
<!-- 当渲染层同合成层有不同的裁剪容器(clipping container)时,该渲染层无法压缩(squashingClippingContainerMismatch)。 -->
<ul id="list"></ul>
<script>
var template = function (i) {
return [
'<li class="box">',
'<p class="inner">asume overlap, 因为 squashingClippingContainerMismatch 无法压缩</p>',
'</li>'
].join('');
};
var size = 20;
var html = '';
for (var i = 0; i < size; i++) {
html += template(i);
}
document.getElementById('list').innerHTML = html;
</script>
</body>
使用chrome devtool来看layers情况
列表项的层级全部被展开了,这种情况下渲染所需时间会更多,就会造成卡顿。
修复办法是:在动画元素中设置position:relative; z-index:1
样式,将动画元素层级提高或者去除box的overflow: hidden
。
总结
堆叠上下文的概念是有点抽象,最好结合实例来理解。
其中要重点掌握的有:
- 堆叠上下文的特性
- 堆叠顺序和两个堆叠的准则
- 常用的触发堆叠上下文的方法
最后感谢下公司同事开的堆叠上下文分享会。