前言:
前不久,同事S遇到了一个关于position和z-index的问题。 他折腾了一天没搞定,群发了邮件寻求帮助, 我一开始以为很简单,就主动说帮忙,简单尝试之后,才发现貌似没那么简单。 问题主要围绕position和z-index。
问题:
我们项目用的是react,同事S有一个table,table里有row,这个table用了一个第三方component,具体什么component无所谓了,总之这些row都有一个css属性:
positon: absolute;
然后,在每个row中有个自定义的下拉框组件,下拉框点击后,会弹出一个选项的容器,他期望选项(options)的容器div(暂称div-options)在页面的顶层,也就是不会被其他东西遮盖。但是结果就是被当前row之后的row给遮盖了。div-options有如下css属性:
position: fixed;
问题的初始状态类似如下例子:
https://codepen.io/bee0060/pen/GGvMxb
在我的例子中,可以简单的对div-options添加大于0的z-index属性解决。 只是在我同事的本地,貌似仅仅这样无法解决,具体原因当时没有意识到所以也就没有细查。 不过我现在的猜测是,每个row上面都有z-index属性,类似如下例子:(没错,该例子中div-options会被后面的row遮盖)
https://codepen.io/bee0060/pen/PaVwvj
在发现只给div-options增加z-index无法解决问题后,我也尝试建议我同事给每个row增加一个z-index,而且值小于div-options的z-index, 但是div-options依然被后面的row遮盖。
当时我觉得真是不可思议,整个人都懵了。
后来试了几种办法没成功就下班回家了。 在下班路上我好想意识到了什么,就回家试了下,发现了以下两种解决方案:
1. 在所有row有z-ind"Wx的情况下, 给div-options所在的row一个较大的z-index值,这样div-options无需z-index也不会被遮盖 , https://codepen.io/bee0060/pen/eKepZG
2. 将div-options提取到table之外,让他们没有父子层级关系,再给一个较大的z-idnex也可以解决,https://codepen.io/bee0060/pen/yEozro
在当前项目环境下,考虑到我们用的是react,我建议我 同事采用第二个解决方案。因为在显示div-options时去修改所在row的z-index,这个操作要影响的对象太多了,而且会对上级(父级)组件造成影响,感觉不太好,要知道,影响的组件越多,就会触发越多的diff和re-render,所以我还是希望这个操作只需要通知尽可能少的组件就能完成。
问题是解决了,但是到底这问题是怎么引起的呢?
我查阅了一些资料, 下面的是我的一些分析和猜想,主要面向希望快速解决问题并获得一个大概思路的朋友。
如果需要更详细准确的解释,我需要查阅更多资料后另开一篇博客,或者你们可以看下其他更详细的博客,正好这就有一篇我感觉不错的。
好,那么下面我要开始表演了。
原因分析:
首先,有些废话还是要说。
我列一下关键的分析依据:
1. 只有已经定位的元素(即position属性值是非static的元素)的z-index属性会生效
2. 在同一堆叠中,z-index值大的遮盖 小的对象
3. 在同一堆叠中,若z-index值相同,后面的对象遮盖前面的对象。
4. z-index为数字(包括0)的元素会在当前堆叠上下文(暂称为堆叠A)中创建堆叠层级(暂称堆叠B),堆叠B可视为堆叠A的子级堆叠。
5. 一个对象只与处于相同堆叠上下文的对象进行z-index的比较并决定哪个遮盖哪个。
6. 若A 和B对象处于同一 堆叠上下文,且根据z-index规则已确定B显示在A上面,那么堆叠B及其所有子级堆叠都将显示在堆叠A以及堆叠A的子级堆叠之上。(因为子级堆叠的显示顺序无法超出父级堆叠的顺序,无论z-index的数值多大)
上面多次出现”堆叠“这个名词,堆叠可以理解为css空间中,延Z轴方向互相层叠的多个小空间。 很多博客在图例中把堆叠画得像一张纸一样,其实并不准确,因为堆叠中可以有子级堆叠,里面可以有无限个层级,所以我觉得理解为空间更合适。 可以理解成较扁平的空间。 而css空间(或你叫DOM或HTML空间也可以)中默认会创建一个堆叠。
看完上面几条,估计大家也有头绪了。 在css空间里,如果对象没有被定位(position不为static)或已定为但z-index不为数字, 那么该对象会被至于父级元素所在的堆叠之内,否则会在父级对象所在的堆叠中创建新的堆叠。 一般情况下,如果页面中全都是不会创建堆叠的对象(未定位或z-index不为数字),那么全部对象都将被置于默认堆叠内。
而每个堆叠内的子堆叠都无法超出其父堆叠的顺序进行显示,如果把堆叠理解成空间,这个应该比较好理解了。用修仙小说的话说就是:张三是某个世界里的人,他再牛也不能超出你所在的世界(当然一般修真小说写的都是能超出,你能理解我的话就好)。
那么结合之前遇到的问题就好理解了。
- 默认情况下,row只有position:absolute, 而都没有z-index时,他们都处于默认堆叠中,且按row的前后顺序进行遮盖, 而div-options属于某一个 row,按顺序的话,他顶多和他所在的row使用一个显示顺序,所以自然会被后面的row遮盖。
- 单独给div-options添加大于0的z-index后, div-options会在默认堆叠(即所有row所在的堆叠)上创建堆叠层级,且由于z-index大于0,在与处于同一父级堆叠中的所有row对象比较后,由于row都没有z-index,显示顺序和z-index为0时相同,所以div-options会显示所有在row之上,也就不会被遮盖了。
- 在给row都增加一个数字的z-index(即使是0)之后,后续的rows又会遮盖div-options。因为指定了数字z-index的row都会在父级堆叠中创建自己的堆叠,而div-options会在其父级row的堆叠内再创建堆叠。 而div-options的父级row(暂称rowP)只会与处于同一个父级堆叠的其他row的堆叠进行显示顺序比较, 所以后续row会显示在rowP之上。而div-options是rowP的堆叠的子堆叠,所以无论div-options的z-index多大,都无法超出rowP所在的堆叠。所以div-options会被后续rows遮盖。
所以我的两个解决办法就是从两方面 着手:
1. 给rowP更大的z-index,让其显示在所有兄弟rows之上,那么div-options无论有无z-index都不会被后续row遮盖。
2. 将div-options抽出来,不再作为rowP的子对象,也就不会再守rowP的堆叠顺序影响,进而可以实现让div-options不被后续row遮盖,因为div-options与所有rows可以处于同一个父级堆叠中,谁先谁后就只由他们各自的z-index值决定。
好了,分析思考和猜测就到这了,本来想尽量简短,但还是写了那么多。
最后说句最重要的: 如有谬误,欢迎指正!
后话:
在解决过程中,没在网上找到该问题的相关资料’ 我(虽然后来找到了),而且自己之前研究也不深,本着DRY原则,避免下次遇到又要花时间折腾,故写下此文。
参考资料:
2. 《CSS权威指南》