现象

起因是有一个需求,要做出有渐变色的阴影。那我们知道,要做阴影可以直接用css3的box-shadow属性,非常方便。但是box-shadow的阴影颜色是只支持rgba的,并不支持渐变效果,所以渐变色的阴影不能直接用box-shadow来做。所以我就换了个方法,用after伪类(或者一个子节点)自己来画阴影。那么快速的画出这个阴影

第一种
<--html-->
<div class="node"></div>

&lt;--css-->
.node {
  position:relative;
  background:green;
  width:100px;
  height:100px;
}

.node::after {
  content:'';
  width:100px;
  height:100px;
  position:absolute;
  background:linear-gradient(90deg,#4f5aff 0,#4242e1 100%);
  top:50px;
  left:50px;
}

然后看下效果

很明显,伪类或者子节点,默认肯定是在父节点上面的,那能不能用z-index把他沉下去呢?第一次尝试,把父节点的z-index设为2,子节点设为1

第二种
.node {
  position:relative;
  background:green;
  width:100px;
  height:100px;
  z-index:2
}

.node::after {
  content:'';
  width:100px;
  height:100px;
  position:absolute;
  background:linear-gradient(90deg,#4f5aff 0,#4242e1 100%);
  top:50px;
  left:50px;
  z-index:1;
}

看下效果

并没有任何变化,是子节点的z-index不够小吗?那么把子节点的z-index设为负值

第三种
.node {
  position:relative;
  background:green;
  width:100px;
  height:100px;
  z-index:1
}

.node::after {
  content:'';
  width:100px;
  height:100px;
  position:absolute;
  background:linear-gradient(90deg,#4f5aff 0,#4242e1 100%);
  top:50px;
  left:50px;
  z-index:-1;
}

再看下效果

还是没有变化。现在把父节点的z-index去掉或者设为auto

第四种
.node {
  position:relative;
  background:green;
  width:100px;
  height:100px;
}

.node::after {
  content:'';
  width:100px;
  height:100px;
  position:absolute;
  background:linear-gradient(90deg,#4f5aff 0,#4242e1 100%);
  top:50px;
  left:50px;
  z-index:-1;
}

再看看效果

子节点终于到了父节点下面,也就是说,只有当父节点没有z-index或者z-indexauto,且子节点z-index为负值时,子节点才会到父节点的下面

事情还没有结束,如果把这个带阴影的组件放到一个新的容器中去,像这样。

第五种
&lt;--html-->
<div class="parent">
  <div class="node">
  </div>
</div>

&lt;--css-->
.parent {
  background:red;
  width:200px;
  height:200px;
}

看下效果

发现由于阴影的z-index是负值,所以它甚至在parent节点的下面,这就不是我想要的了。当然,这时候你可以把parent节点的z-index设为-2,让它去更下面,但这又带来了另一个问题,假如node节点上有点击事件,会因为这个z-index=-2而失效。

第六种
&lt;--html-->
<div class="parent">
  <div class="node">
    <a onclick="alert(1)">click</a>
  </div>
</div>

&lt;--css-->
.parent {
  background:red;
  width:200px;
  height:200px;
  position:relative;
  z-index:-2;
}
.node {
  position:relative;
  background:green;
  width:100px;
  height:100px;
}

.node::after {
  content:'';
  width:100px;
  height:100px;
  position:absolute;
  background:linear-gradient(90deg,#4f5aff 0,#4242e1 100%);
  top:50px;
  left:50px;
  z-index:-1;
}

这样做,虽然ui上符合要求,阴影是正确的显示了,但是点击click并不会有任何效果。要让这个阴影组件正确显示且能进行交互,需要把parent节点的z-index设为1。

第七种
&lt;--html-->
<div class="parent">
  <div class="node">
    <a onclick="alert(1)">click</a>
  </div>
</div>

&lt;--css-->
.parent {
  background:red;
  width:200px;
  height:200px;
  position:relative;
  z-index:1
}
.node {
  position:relative;
  background:green;
  width:100px;
  height:100px;
}

.node::after {
  content:'';
  width:100px;
  height:100px;
  position:absolute;
  background:linear-gradient(90deg,#4f5aff 0,#4242e1 100%);
  top:50px;
  left:50px;
  z-index:-1;
}

这样,这个阴影组件就可以正常显示,并且不影响交互。

另外,如果在parent节点中加上一个slot节点的兄弟节点other,slot和other的显示关系就是普通的z-index比大小。

结论

最终通过这样的四层父子节点的结构,可以得出结论:

  1. 父子节点之间,子节点默认在父节点之上,除非子节点的z-index为负且父没有z-index(或为auto
  2. 为了让z-index为负的节点只在他的父节点之下,而不在更外层的节点之下,需要给父节点再套上一层z-index>=0的节点
  3. 兄弟节点之间还是直接比较z-index的大小,如果一样就比先后顺序。

原理

在w3c的文档中,关于z-index的描述里,有一个叫做堆栈上下文(stacking contexts)的概念,在同一个堆叠上下文中,元素按照一定的顺序进行堆叠。文档中的顺序为(从下到上)

  1. 构成堆栈上下文的根元素。
  2. 堆栈级别为负的子元素(小的优先)。
  3. 非行内、没有position的元素。
  4. 没有positionfloat元素。
  5. 行内、没有position的元素,包括table和block。
  6. 堆栈级为0的子堆栈上下文和堆栈级别为0的position元素。
  7. 堆栈级大于0的子堆栈上下文(小的优先)。

而关于什么时候会产生新的堆栈上下文,文档中给了三种情况。

  1. 根元素(html)
  2. 任何有定位且z-index不为auto的元素
  3. 未来的css中,一些其他的属性也可能引入堆栈上下文,例如不透明度(opacity

再来回头看看之前的几个例子。

第一种情况,没有z-index,都在同一个堆栈上下文中,那就是按照父子关系进行堆叠。

第二种和第三种情况类似,父节点的z-index设为2,子节点为1或者-1。此时,父节点已经产生了一个新的堆栈上下文,并且父节点是这个上下文的根元素,所以不管子节点有没有z-index,一定在上面。

第四种情况,父节点的z-indexauto,子节点为负。此时父子节点在同一个堆叠上下文中,并且根元素是html,父节点不是根元素,根据堆叠顺序,堆叠级别为负的元素在下。

第五种情况和第四种情况类似,parent节点和node节点情况相同,在同一个堆栈上下文忠且都不是根元素,所以都在子节点上面。

第六种情况和第七种情况类似,只要给parent节点加上一个z-index,就产生了新的堆栈上下文,且parent节点是这个上下文的根元素,所以会在最下面。至于这个z-idnex是正是负,对于堆叠顺序并没有影响,设为任意非负值就可以。

同样的,由于opacity也可以产生新的堆栈上下文,所以给6、7中的parent节点加上一个opacity:0.99的属性,也可以达到一样的效果。


Jovi
38 声望1 粉丝

想要尽力保住头发的前端程序员