blog
blog copied to clipboard
z-index 与堆叠上下文(stacking context)
最近有划词翻译的用户报告了一个 bug,说是语种选择的弹窗被文本框、翻译结果和切换语种的小三角箭头遮挡了,如下图:
我首先检查了它们的 z-index
属性,发现这些遮挡了弹窗的元素都没有设置过 z-index,那为什么会遮挡住设置了 z-index 的弹窗?
谷歌一番之后,我发现了 CSS 中一个新的概念——堆叠上下文(stacking context)。
上图中的 HTML 结构可以简化为:
<html>
<div style="transform: translateY(0px)">
<div>定位元素</div>
<div style="position: absolute; z-index: 20">弹窗</div>
</div>
<div style="position: relative">文本框</div>
<div style="transform: translateY(0px)">翻译结果</div>
</html>
按照我的理解,这其中只有弹窗设置了 z-index,那么它应该显示在最上方,但事实上它却被文本框和翻译结果遮挡了,原因是:弹窗的父元素是一个新的堆叠上下文,所以弹窗的 z-index
只在它的父元素范围内才生效;而文本框和翻译结果也分别创建了新的堆叠上下文,在 DOM 中排在后面的堆叠上下文会显示在上面。
MDN 上说
position: relative
加上值不为auto
的z-index
组合起来才能创建一个新的堆叠上下文,但我在 Chrome 中测试发现,只需要有position: relative
就会创建一个新的堆叠上下文。
知道问题的原因之后,解决方案也就有了:
- 修改 CSS。例如,去掉文本框和翻译结果的
transform: translateY(0px)
和position: relative
- 如果不方便修改 CSS(比如这些 CSS 是由框架生成的),那么可以改变弹窗在 DOM 中的位置,例如:
<html>
<div style="transform: translateY(0px)">
<div>定位元素</div>
</div>
<div style="position: relative">文本框</div>
<div style="transform: translateY(0px)">翻译结果</div>
<div style="position: absolute; z-index: 20">弹窗</div>
</html>
- 如果 DOM 也不方便修改,那么 stackoverflow 上还提到了一个办法,不过似乎只对用
transoform
生成的堆叠上下文有效。