在使用 bootstrap 的模态框时,出现了半透明遮罩始终盖住模态框的情况,
只有把模态框的 HTML 写到 body 之下才恢复正常,
当时郁闷得对着屏幕举了半分钟的中指。(程序员最好看看佛经,学会清心寡欲)
后来有幸看到了张鑫旭在慕课网 CSS深入理解之 relative 的视频,
才算真正知晓了层叠上下文在 CSS 中的规则和运用技巧。
还有些名称比较类似的学术语言和概念,格式化上下文、执行上下文、音频上下文、2D上下文等,感兴趣地可以去搜一下玩玩。
废话有些多,下面就正式开始吧!
什么是层叠上下文和层叠水平
层叠上下文其实把它理解为 z 轴也没有问题,屏幕上面叠着一层层图层(不是所有的元素都是)。
层叠水平则表示同一层层叠上下文对应的图层。
创建层叠上下文
那么怎样才能产生层叠上下文,让它的 z 轴不一样呢?
以下情况会创建层叠上下文(随着 CSS3 属性还在增加,本表不全):
- z-index 不为 auto 的 position 定位元素
- z-index 不为 auto 的 flex
- opacity 不为 1
- transform 不为 none
- perspective 不为 none
- filter 不为 none
- mix-blend-mode 不为 normal
- 带有 isolation: isolate
- will-change 不为 none
- 带有 -webkit-overflow-scrolling: touch
<style>
.box {
width: 100px;
height: 100px;
margin: 0 auto -50px;
}
.box1 {background: pink}
.box2 {background: grey}
.box1 {left: 10px}
.box1 { transform: translate(0,0) }
</style>
<div class="box box1"></div> <!-- 粉色 -->
<div class="box box2"></div> <!-- 灰色 -->
20171122221904
此例中 .box1 创建了层叠上下文,z 轴上就比普通元素层级更高了,因此覆盖了 .box2。
同级层叠水平下的层叠顺序
而当有两个层叠上下文时,它们就有了兄弟和父子关系(假设现在都处于同级层叠水平)。
当为兄弟关系时(层叠上下文的兄弟关系,非 DOM 的兄弟),它们总是 后者居上 的。
<style>
.box {
width: 100px;
height: 100px;
margin: 0 auto -50px;
}
.item {
/* 两个 dom 中子级创建层叠上下文,构成兄弟关系的层叠水平 */
transform: translate(0,0);
width: 90px;
height: 90px;
top: -10px;
}
.box1 {background: pink}
.box2 {background: grey}
.item1 {background: red}
.item2 {background: green}
.item1 {left: -10px;}
.item2 {left: -20px;}
</style>
<div class="box box1"> <!-- 粉色 -->
<div class="item item1">item1</div> <!-- 红色 -->
</div>
<div class="box box2"> <!-- 灰色 -->
<div class="item item2">item2</div> <!-- 绿色 -->
</div>
<!-- .item 变为层叠上下文,所以比 .box 这种普通元素层级更高. -->
<!-- 且 .item 遵循后者居上,所以 .item2 覆盖了 .item1 -->
20171122162647
当为父子关系时,子级就存在父级的“作用域”内了,父级高我才高,不然我再高也没用。
<style>
.box { position: relative; z-index: 0 }
.item1 { z-index: 999; }
/* 紧跟上例,本来 .item2 由谁后谁上的原则位于上面。 */
/* 当现在其父级 .box 也创建的层叠上下文,且遵循谁后谁上,因此 .box2 要高于 .box1。 */
/* 此处 .item1 的父级已经低于 .box2 了,本身层级再高也还是在 .box1 的范围内而已。 */
</style>
20171122164912
注:此处 position: relative; 但不写 z-index: 0(即 z-index: auto),它会覆盖普通元素,但并不会创建层叠上下文。 下面这个也是类似的例子,
.box { position: relative; margin: 0 0 10px }
.item {
position: relative;
z-index: -1;
}
.box2 { z-index: 0; }
/ 只有 z-index 不为 auto 才创建层叠上下文 /
20171123134429
不同级层叠水平下的层叠顺序
既 z-index 不为 auto 的情况,这时它们总是 谁大谁上 的。
<style>
.box1 { z-index: 2; }
/* 这个就很好理解了,.box 现在是兄弟关系,.box1 的 z-index 更大,那么它就更高 */
</style>
20171122173308
关于最初 BUG 的解读
回到最初那个 bootstrap 内容框在黑底下的问题,就很方便理解了。
<div class="content">
<div class="modal"></div> <!-- modal 内容框 -->
</div>
<div class="modal-backdrop"></div> <!-- 黑色半透明底 -->
.modal 和 .modal-backdrop 都定位了,成为了层叠上下文,而如果 .content 由于需要或误操作也创建了层叠上下文,那么 .content 和 .modal 就有了兄弟关系,谁后谁上+谁大谁上,最终造成黑底的层级高于了 .content 的层级,而 .modal 作为 .content 范围内子级也就因此被覆盖住了。
最终,要么我们把 .modal 从 .content 拿出放置于与 .modal-backdrop 同级,要么把 .content 的层级提高得比 .modal-backdrop 更大(如果 .content 还有背景色那就是另一回事了)。
所以个人认为 bootstrap 这样写是不好的,要遇上一个不懂层叠上下文的,想破头也不知道为什么。
我推荐下面的这种写法:(自创的,后来看 layui 等框架也是这样写的,看来我没做错)
<style>
.modal {
position: absolute;
top: 0; bottom: 0;
left: 0; right: 0;
opacity: 0;
z-index: -1;
overflow: hidden;
transition: opacity .3s, z-index 0s .3s;
}
.modal-bg {
position: absolute;
top: 0; bottom: 0;
left: 0; right: 0;
opacity: 0;
transition: opacity .15s;
background: rgba(0,0,0,.8);
}
.modal.in {
opacity: 1;
z-index: 1;
transition: opacity .3s, z-index 0s;
}
.modal.in .modal-bg {
opacity: 1;
}
.modal-box {
transform: translate3d(0,0,0);
}
</style>
<div class="modal">
<div class="modal-bg bg"></div> <!-- 黑底 -->
<div class="modal-box">内容</div>
</div>
就像张鑫旭大神所说,如果你理解了层叠上下文,那么就根本不需要把 z-index 设得很大了,1-2 足矣。
非层叠上下文的覆盖关系
在张大神的 此文 中存在着这样一张图,一直让我产生着奇怪的误解。
后来才想通,这是非层叠上下文时的覆盖关系,但很容易让我们产生混淆。
比如 float / inline 等,和 relative 下 z-index 非 auto 一样,都是会产生覆盖,但不会创建层叠上下文。
里面的 background 和 z-index 真的实在是蛊惑人心,个人认为大可忽略不论。
网友评论