CSS必知必会:从z-index到层叠上下文

一个小问题

实现一个按钮以下图:css

设计图

首先能够想到用box-shadow,可是很遗憾,设计稿是用了两个圆角矩形叠加的,下面的是纯色背景,上面的是一个半透明彩色径向渐变,造成的效果不是吸管取色能hold住的。因此仍是老实按照设计稿来实现。给按钮加个after伪元素好了。html

codepen.io/pujiaxun/pe…web

把上面的问题抽象一下:浏览器

.grandfather A
  .father B
    .son C
复制代码
.grandfather {
  width: 300px;
  height: 200px;
  background-color: #999;
}

.father {
  width: 200px;
  height: 150px;
  background-color: #acc;
  position: relative;
}

.son {
  width: 100px;
  height: 100px;
  background-color: #c88;
  position: absolute;
  bottom: -40px;
}
复制代码

其实目标就是让子元素C(即按钮的纯色背景)介于父元素B的背景与爷爷元素A的背景之间。ide

按照我以前错误的理解,我想给C加一个z-index: -1,就可让C在其父元素B的背景后面。然而并无。甚至能够说我彻底理解错了…wordpress

层叠顺序(Stacking Order)

怎么解决?先介绍一个概念——层叠上下文(stacking context)。相似BFC,一个比较抽象的概念。根据设定,在一个具体的层叠上下文中,浏览器会依据层叠顺序规则,对上下文中的全部元素进行排序。学习

首先在了解这些名词以前,咱们靠着丰富的CSS经验能够猜到,一个子元素,默认会显示在父元素的背景之上。废话啦,要不还能叫背景吗?以下图,绿色子元素在灰色父元素之上,也能够说是在Z轴上更靠近用户的位置。flex

再稍微有点经验的能够发现,inline/inline-block的元素比block元素更高一点。以下图,绿色元素是inline-block,紫色(?玫红色?)元素是利用负margin向上偏移的block。但做为文档流的后来者,理当后来居上,却被挡在了下面。ui

这时候我要掏出一张层叠顺序天梯图了spa

简而言之,若是你们都是普通block元素,按照文档流后来居上,符合常识。但若是我段位比你block元素更高,好比我是inline-block选手,那你还木有资格和我PK,轮不到你和我比什么文档流顺序,甚至都不用看你爹是谁。好比下图

按照直觉,紫色元素在绿色元素的下面,那紫色元素的内容B就应该被挡住不显示。然而并无,B做为一个文本节点,默认是inline,在天梯排名中比绿色block更高,尽管它是叔叔元素(原谅我这么随便的起名……)

那怎么用z-index呢

这又要说回层叠上下文了。

一个层叠上下文能够理解成是一种特殊段位,它能够和z-index值组合,造成一个具体的段位。 我写了一个更详细点的图(CodePen地址

好比一个z-index为-1的层叠上下文,它的段位就在蓝色的级别。

层叠上下文

那到底**什么是层叠上下文啊?

就像BFC,当一个元素在某些特殊状况下,就会变成一个层叠上下文。举个简单的例子,元素X绝对定位,而且z-index: 1000,它就造成了一个层叠上下文,其段位(层叠顺序)至关高。

而一个层叠上下文一旦造成,整个事情会有一些变化。元素X下的全部子元素将再也不参与元素X之外的级别PK,只会在这个最近的上下文中去比较。 (还记得刚刚那个欺负叔叔元素的B吗?)

除了绝对定位配合z-index属性,还有什么办法造成层叠上下文呢?参考MDN:

  1. 根元素 (HTML)
  2. z-index 值不为 "auto"的 绝对/相对定位
  3. 一个 z-index 值不为 "auto"的 flex 项目 (flex item),即:父元素 display: flex|inline-flex
  4. opacity 属性值小于 1 的元素(参考 the specification for opacity)
  5. transform 属性值不为 "none"的元素
  6. mix-blend-mode 属性值不为 "normal"的元素
  7. filter值不为“none”的元素
  8. perspective值不为“none”的元素
  9. isolation 属性被设置为 "isolate"的元素
  10. position: fixed
  11. 在 will-change 中指定了任意 CSS 属性,即使你没有直接指定这些属性的值
  12. -webkit-overflow-scrolling 属性被设置 "touch"的元素

看起来与合成层(Compositing Layer)的造成条件,有那么点殊途同归。

等一哈

有个小问题,这层叠顺序说的这么好听,好像有个地方漏了。一个绝对定位的元素,彷佛也是高于普通兄弟元素(正常文档流中)的,即便没有设置z-index。它不是层叠上下文啊?属于什么段位呢?

定位元素属于 z-index: 0/auto 段位。 但须要解释一下下,首先这个段位,W3的定义是

the child stacking contexts with stack level 0 and the positioned descendants with stack level 0.

当你使用了非static定位属性,例如absolute定位,就会自动生成z-index: auto。还有其余例如transform造成的层叠上下文,若是没有指定z-index,也默认变为auto,和定位元素同段位,遵循后来居上原则。

那一个普通元素,直接写个z-index: auto会怎么样呢?

能够,你很热爱学习。答案是没用的,由于它既不是“stacking contexts”,也不是“positioned descendants”。

一个层叠上下文的z-index为auto时,能够看成0看待。可是定位元素的z-index为auto时,不能和0等同,由于它不是层叠上下文。还记得吗,定位元素配合数值z-index会造成层叠上下文。它只是定义中的一种特殊状况。

回到需求

.grandfather A
  .father B
    .son C
复制代码
.grandfather {
  position: relative;
  z-index: 0;
}

.father { 
  position: relative;
}

.son { 
  position: absolute;
  bottom: -40px;
  z-index: -1;
}
复制代码

给grandfather元素设置相对定位,以及z-index: 0,便可将grandfather生成一个层叠上下文,此时father元素至关于这个上下文中的z-index: 0/auto段位,再给son元素设置一个负数z-index,便可造成一个 z-index: -1 级别的层叠上下文,理应排在father元素后面,在grandfather的背景前面,达成理想。

而在最一开始,我觉得只要给son设置一个z-index: -1,结果变成了下图:

son都被grandfather挡住了,由于son的父元素father、grandfather,都不是层叠上下文,他们共同处在更上级的层叠上下文中(好比html根元素)。因此son还要和grandfather去比较段位,结果son是-1级层叠上下文,没有普通block的grandfather段位高,就被挡住了。

总结

三类层叠上下文

  1. HTML根元素天生具备层叠上下文,称之为“根层叠上下文”。
  2. z-index值为数值的定位元素的“传统层叠上下文”。
  3. 其余CSS3属性,参见MDN

层叠排序规则

  1. 一个元素A向上查找最近的层叠上下文B,A只须要和B的后代元素进行比较
  2. 一个层叠上下文自己和别的元素比较顺序时,看做一个总体,内部不需考虑
  3. 层叠级别优先,同级别的遵循文档流后来居上原则。

z-index不必定是必要的

学完这么多,其实会发现不少时候不须要用z-index,好比凭经验就知道,绝对定位的元素通常会更高,再好比如今学到了inline元素也很厉害。更不要说z-index:1000000000这种操做了,固然,避免z-index的滥用又能够是另外一个话题了。

就这么简单吗

凡事涉及到浏览器的具体实现,它就很差玩了。各家浏览器会有各类刁钻的差别,好在大部分使用还比较正常,可能配合上position: fixed、各类transform、filter、: hover伪类等,我只能说 good luck have fun~

参考文档

深刻理解CSS中的层叠上下文和层叠顺序

层叠上下文_MDN

相关文章
相关标签/搜索