lmk123 / blog

个人技术博客,博文写在 Issues 里。
https://github.com/lmk123/blog/issues
623 stars 35 forks source link

z-index 与堆叠上下文(stacking context) #95

Open lmk123 opened 2 years ago

lmk123 commented 2 years ago

最近有划词翻译的用户报告了一个 bug,说是语种选择的弹窗被文本框、翻译结果和切换语种的小三角箭头遮挡了,如下图:

image

我首先检查了它们的 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 加上值不为 autoz-index 组合起来才能创建一个新的堆叠上下文,但我在 Chrome 中测试发现,只需要有 position: relative 就会创建一个新的堆叠上下文。

知道问题的原因之后,解决方案也就有了:

  1. 修改 CSS。例如,去掉文本框和翻译结果的 transform: translateY(0px)position: relative
  2. 如果不方便修改 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>
  1. 如果 DOM 也不方便修改,那么 stackoverflow 上还提到了一个办法,不过似乎只对用 transoform 生成的堆叠上下文有效。

相关链接