你不知道的CSS堆叠上下文(Stacking Context)

3,961 阅读5分钟

对于堆叠上下文,你可能没有听说过这个术语,但是你肯定有接触过它。当你在使用绝对定位和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

总结

堆叠上下文的概念是有点抽象,最好结合实例来理解。

其中要重点掌握的有:

  • 堆叠上下文的特性
  • 堆叠顺序和两个堆叠的准则
  • 常用的触发堆叠上下文的方法

最后感谢下公司同事开的堆叠上下文分享会。