[一文读懂系列]CSS层叠上下文与z-index之从理论到实战

236 阅读10分钟

前言

好的东西就要拿出来分享,分享使人进步。最近抽空把这块知识点给吃透了,然后赶紧整理成材料分享给大家,希望能帮到所有看到的人~

1 理论规范

为什么浏览器渲染出来的效果和我预期的不一样呢?浏览器也是按照一定规范去实现对应的渲染规则的,为了更好的理解这块的渲染规则,我把CSS2.1规范中关于z-index这块进行了翻译 CSS2.1规范z-index部分,感兴趣的同学可以多驻足停留一下。当然,这个不是必须的,下面我会用更通俗易懂的方式进行讲解。

2 理解概念

2.1 层叠上下文(stacking context)与Z轴

二维平面
在大部分时候,我们感知到页面是二维的。这个很好理解,如上图所示,水平方向就是X轴,竖直方向就是Y轴。那么Z轴改如何理解呢?(有没有人注意到上图偏下位置有3个红点点……)
眼睛
我们姑且把上图理解为眼睛,眼睛看着屏幕,屏幕对直眼睛的这条线就可以认为是Z轴。还是不好理解?我们换个角度再来看问题,下面附上一张动图(注意:为了避免视觉轰炸,默认只播放一次哦,如果没有看到动效,可以对着图片右键“在新标签页中打开图片”)。
三维空间
现在可以理解什么是Z轴了吧。而层叠上下文其实就是对HTML元素的一个三维概念的解释。引用CSS2.1规范中的原话来小结就是:每个盒模型元素在三维空间中都有一个位置。除了水平和垂直位置之外,这些盒模型元素还会沿着“Z轴”进行放置,并在一个盒模型元素上对另一个盒模型元素进行格式化。当盒模型元素之间在视觉上重叠时,Z轴位置就特别重要。

划重点:

  • 层叠上下文在一个HTML文档树中是可以有多个的,而引发创建层叠上下文的盒模型元素,我们称之为层叠上下文元素;
  • html根元素是一个默认的层叠上下文元素;
  • 给定位元素设置z-index属性,且属性值为一个整数,该元素也会成为一个层叠上下文元素。

2.2 层叠水平(stack levels)与z-index

为什么把层叠水平和z-index放在一起说明呢,因为这两个其实是紧密相连的。层叠水平可以按照字面上来进行理解,由于在层叠上下文中,盒模型元素之间可能会出现视觉上的重叠,层叠水平就是用来描述当重叠出现的时候,谁叠在上面,谁叠在下面的一个水平层级概念。而z-index就是用来指定层叠水平的具体数值的。

划重点:

  • z-index的具体数值,就是指代的在当前层叠上下文里,该元素的具体层叠水平;
  • 你可以通过z-index去尝试指定层叠水平,但需要注意,只有对定位元素(除static之外的定位)使用才会生效;
  • 当指定z-index的值是一个具体整数的时候,并且是定位元素(除static之外的定位),那么会在该元素创建一个新的层叠上下文。

2.3 层叠顺序(stacking order)

既然层叠水平已经是用来描述层叠谁上谁下的,那么为什么还要有一个层叠顺序呢,他们之间又是一个什么关系呢?层叠顺序可以理解为进行层叠比较或者渲染的一个具体规则。但是这个规则比较复杂,为了方便理解,可以由叠放的上下顺序大致地划分出若干等级来,而这个等级就是层叠水平。具体的层叠顺序如下图所示:

层叠顺序

2.3.1 顺序解读(越后面的层叠顺序越大):

  1. 导致创建层叠上下文的元素的背景、边框;
  2. 层叠上下文的子层叠上下文元素,并且z-index设置的是负值;
  3. 简单理解为块元素,但有些附加条件;
  4. 简单理解为浮动元素,但有些附加条件;
  5. 简单理解为行元素,但有些附加条件;
  6. 这个比较抽象,在2.3.2将进行详细讲解;
  7. 层叠上下文的子层叠上下文元素,并且z-index设置的是正值。

2.3.2 层叠水平为0:

层叠水平为0的元素,有两种情况会导致出现层叠水平为0的元素。

  1. 定位元素(除static之外的定位)设置z-index为0;
  2. 普通元素通过设置了某些css属性导致层叠水平自动提升为0。以下情况会导致层叠水平自动提升到0:
    • opacity值不为1的元素
    • transform值不为none的元素
    • 设置了z-index为具体数字数值,且父元素为flex的元素
    • 使用了css3 filter的元素
    • 使用了will-change进行加速的元素
    • mix-blend-mode值不为normal的元素
    • isolation值为isolate的元素
    • overflow或者overflow子属性的值有一个为scroll、auto的元素(IOS 移动端)

3 方法论

上面规则太复杂?我整理了一个简约版,囊括了大部分要点,可以胜任绝大部分场景(如需KO所有场景,还是需要严格按2.3的层叠顺序来进行比较)。

3.1 比较方法简约版

  1. 正z-index层叠上下文元素 > 层叠水平为0的元素 > 普通元素
  2. zIndex大则大(层叠上下文元素间比较适用)
  3. 后来居上原则,文档中后面出现者大

划重点:

  • 序号数字代表优先使用的比较条件,只有当双方条件不分胜负,那么再套用下一个条件;
  • 层叠上下文可以嵌套,所有子元素(普通元素、层叠上下文元素)显示层级均受制于所属层叠上下文;
  • 两个进行比较的元素所处的最近的一个层叠上下文,必须是同一个层叠上下文,才能直接进行比较。反之则需要向上(父元素)逐层寻找层叠上下文元素,直到满足直接比较条件为止。

4 实战论证

4.1 论证7级层叠顺序

代码:

在线demo

效果:

效果图

说明:

为了方便验证,增强可读性,同时减少其他因素干扰。html采用结构简单,最外层一个父元素.box-wrap,里面直接排列7个子元素。可以看到如下结果:

  • 父元素是一个定位元素,并且设置了z-index=0,导致这个元素变成了层叠上下文元素。但依据7级层叠顺序,所以它显示在了最底层;
  • 第7个子元素.sub-box7,它在文档流中是最后出现,但是它是一个定位元素,同时设置了z-index是一个负值,所以它显示在了倒数第二层;
  • 第6个子元素.sub-box6,它是一个普通块元素,也没有设置其他影响排布的css属性,所以它显示在了倒数第三层;
  • 第5个子元素.sub-box5,它是一个浮动元素,也没有设置定位,所以它显示在了倒数第四层;
  • 第4、第3、第2这三个子元素放在一起说明,因为它们3个在7级层叠顺序里都属于“层叠水平为0元素”。.sub-4它是一个定位元素并且设置z-index:0,所以它层叠水平是0。.sub-3由于设置了opacity不为0,导致获得层叠水平提升至0。.sub-2由于设置了transform不为auto,导致获得层叠水平提升至0。而最终呈现层级效果是4>3>2,因为层叠水平一致,所以适用后来居上原则,在文档中后面出现的层叠顺序大;
  • 第1个子元素.sub-box1,它是一个定位元素,并且设置了z-index为正数,导致它处于7级层叠顺序里的最高级别,所以哪怕它是第1个子元素,在文档中子元素里最先出现的,实际渲染依然会展示在最上面。

划重点:

  • 第4、第3、第2这三个子元素大家可以自己在demo中调整文档出现先后顺序,就可以看到它们的实际渲染顺序大小也会跟着变。从而验证了它们都是“层叠水平为0元素”这一点。
  • 尝试给第2、第3个子元素设置z-index为一个正整数,可以看到不会影响到实际渲染层级。从而论证z-index只对定位元素有效,并且通过设置css某些属性导致的层叠水平提升到0,也仅仅只是提升了层叠水平,它本身依然不是一个真正的层叠上下文元素。

4.2 论证非兄弟元素的比较

代码:

在线demo

效果:

效果图

说明:

简单介绍一下html结构。如同元素内容所示,A、B、C3个元素互为兄弟节点。而A元素下有一个子元素即为A-1,同时A-1下面又有一个子元素,即为A-1-1。B-1、B-2可以依次类推。此番用于比较优先级的两个元素分别是A-1-1和B-1。为了加深大家的理解,这回在最右边准备了4个按钮,可以用来分别给指定元素设定z-index。

  • 初始状态:A-1-1和B-1两元素所属的最近层叠上下文都是html根元素这个层叠上下文,所以这两个元素可以直接进行比较。它两都是定位元素,并且也都没有设置z-index,所以层叠水平都是0。此时后来居上原则,所以B-1的层叠优先级大于A-1-1;
  • 点击右侧第1个按钮:A-1-1作为一个定位元素设置了z-index为一个正整数,正层叠上下文元素大于层叠水平0元素,所以导致渲染结果A-1-1叠在了B-1上面;
  • 点击右侧第2个按钮:B-1作为一个定位元素也设置了z-index为一个正整数,此时两个层叠上下文元素相比,z-index也相同,那么自然后面出现的优先级高,所以B-1又重新叠在了A-1-1上面;
  • 点击右侧第3个按钮:此时A-1-1的z-index大于B-1,两层叠上下文元素比较,z-index大者层叠优先级大,所以A-1-1又重新叠在了B-1上面;
  • 点击右侧第4个按钮:A-1是一个定位元素,由于设置了z-index为一个整数,导致A-1变成一个层叠上下文元素。此时A-1-1就不能直接和B-1进行比较了,因为A-1-1此时所属的最近一个层叠上下文变成了A-1。所以A-1-1与B-1的比较,就变成了A-1与B-1进行比较。层叠上下文元素比较,z-index大者大,所以B-1会叠在A-1上面。子元素的层叠顺序受制于所属层叠上下文的层叠顺序,所以B-1自然也是叠在A-1-1上面。

结尾

好了,这次分享到这里为止,大家有任何疑问或者问题,都可以在评论区给我留言,我会尽我所能来为大家解答的~