深入浅出 CSS 层叠上下文

我们在编写 CSS 样式时不少遇到这样的疑惑:“为什么这个元素会被覆盖?”、“为什么设置较高的 z-index 值还是不起效果?”

不妨先想想这个问题,当多个 HTML 元素在浏览器视窗中发生重叠时,浏览器会怎么安排哪个元素显示在上、哪个显示在下呢?其实所有元素在发生层叠时的表现都是按照一定的优先级顺序的,这些顺序规则都是建立在 层叠上下文(The Stacking Context) 这个三维概念中,我们一起来了解下。

层叠上下文特性

  • 在同一个层叠上下文中,子元素按照 层叠顺序 规则进行层叠;

    <style>
    .wrapper {
      position: relative;
      z-index: 1;
    }
    .wrapper div:nth-of-type(1) {
      position: relative;
      z-index: -1;
    }
    .wrapper div:nth-of-type(2) { display: block; }
    .wrapper div:nth-of-type(3) { float: left; }
    .wrapper div:nth-of-type(4) { display: inline-block; }
    .wrapper div:nth-of-type(5) { position: relative; }
    .wrapper div:nth-of-type(6) {
      position: absolute;
      z-index: 1;
    }
    </style>
    
    <body>
      <div class="wrapper">
        <div>position: relative;<br>z-index: -1;</div>
        <div>display: block;</div>
        <div>float: left;</div>
        <div>display: inline-block;</div>
        <div>position: relative;</div>
        <div>position: absolute;<br>z-index: 1;</div>
      </div>
    </body>
    

    关于 层叠顺序 的详解请往下看,完整代码已上传 CodePen:https://codepen.io/JunreyCen/pen/WqoLmr

    另外,当不同层叠顺序的元素相比较时,不关心元素在 DOM 树中的结构关系:

    <style>
    .child {
      position: relative;
      z-index: -1;
    }
    </style>
    
    <div class="parent">
      <div class="child"></div>
    </div>
    <div class="parent" style="opacity: 0.6;">
      <div class="child"></div>
    </div>
    

    左边实例中 Parent 元素不是层叠上下文元素,所以和 Child 元素是处于同一个层叠上下文中,而根据层叠顺序,普通流块级元素 是叠在 z-index 定位元素 之上的。右边实例中 Parent 元素指定属性 opacity: 0.6,创建了一个层叠上下文,从而使 Child 元素包含在内,根据层叠顺序 z-index 定位元素 会叠在 根元素背景和边框 之上。这样的表现说明,ParentChild 元素的父子节点关系不会影响层叠关系。

  • 在同一个层叠上下文中,当元素的层叠顺序相同时,按照 元素在 HTML 中出现的顺序 进行层叠;

    <style>
    .box-1 {
      position: relative;
    }
    .box-2 {
      position: absolute;
      top: 50px;
      left: 50px;
    }
    </style>
    
    <div class="wrapper">
      <div class="box-1"></div>
      <div class="box-2"></div>
    </div>
    <div class="wrapper">
      <div class="box-2"></div>
      <div class="box-1"></div>
    </div>
    

    所有 z-index 属性值为 0 / auto 的定位元素的层叠顺序都是相同的,如实例中的 box-1box-2 元素,可以看出它们发生层叠时遵循的是 在 HTML 中出现的先后顺序

  • 层叠上下文支持嵌套,子级层叠上下文对于父级层叠上下文是一个独立单元,对于兄弟元素来说也是相互独立的;

  • 每个层叠上下文都是自包含的,无论在子级层叠上下文内如何 “翻云覆雨”,也是基于父级层叠上下文的层叠顺序下;

    <style>
    .parent-1,
    .parent-2,
    .child-1, 
    .child-2 {
      position: relative;
    }
    .parent-1 { z-index: 2; }
    .parent-2 { z-index: 1; }
    .child-1 { z-index: -1; }
    .child-2 { z-index: 10; }
    </style>
    
    <div class="parent-1">
      <div class="child-1"></div>
    </div>
    <div class="parent-2">
      <div class="child-2"></div>
    </div>
    

    Child-1Child-2 元素分别处于各自的父级层叠上下文元素 Parent-1Parent-2 中。由于 Parent-1 是层叠于 Parent-2 之上的,即使 Child-1z-index: -1 属性值小于 Child-2z-index: 10Child-2 也不能逾越其父级上下文覆盖在 Child-1 上面的。换句话说,子级层叠上下文的层级讨论只在其父级层叠上下文中有意义。

创建层叠上下文

满足以下 12 个条件中任意一个的元素会创建一个层叠上下文(摘自 MDN ):

1、根元素 <html>
2、z-index 值不为 auto 的相对定位(position: relative)和绝对定位(position: absolute);
3、固定定位(position:fixed);
4、z-index 值不为 auto 的伸缩盒模型中的 flex 项目;
5、opacity 属性值小于 1
6、transform 属性值不为 none
7、filter 属性值不为 none
8、mix-blend-mode 属性值不为 normal
9、perspective 属性值不为 none
10、isolation 属性值为 isolate
11、will-change 属性 指定了上述任意一个 CSS 属性(即便没有直接指定这些属性的值)
12、-webkit-overflow-scrolling 属性值为 touch

关于第 11 点中的 will-change 属性:该属性主要用于告知浏览器元素会发生哪些变化,有利于浏览器在元素真正变化之前提前做好优化准备。所以当 will-change 指定了能创建层叠上下文的 CSS 属性(包括 z-index 属性)时,浏览器也会为该元素创建层叠上下文。

层叠顺序

层叠顺序就是元素在层叠上下文中的显示规则。在此之前需要提前认识一个重要概念:z-index 只会对定位元素(非普通流)有效

当元素发生层叠时,会按照下面的 7 阶层叠顺序来决定元素显示的前后顺序:

1、根元素的背景与边框;
2、z-index < 0 的定位元素;
3、普通流中的块元素(display: block);
4、浮动块元素;
5、普通流中的行内元素(display: inline / display: inline-block);
6、z-index 属性值为 0 / auto 的定位元素、其他子级层叠上下文元素;
7、z-index > 0 的定位元素;

  • 普通流:不指定 position 或者 position: static
  • 上面第 6 点中的 其他 子级层叠上下文元素:不依赖 z-index 属性就能创建层叠上下文的元素;
7 阶层叠顺序图

注意事项

  • 层叠顺序的第 6 阶中,z-index 属性值为 0 / auto 的定位元素不依赖 z-index 的子级层叠上下文元素 顺序相同,会遵循 在 HTML 中出现的先后顺序 来层叠;

    <div class="wrapper">
      <div class="box-1" style="opacity: 0.8;"></div>
      <div class="box-2" style="position: absolute;"></div>
    </div>
    <div class="wrapper">
      <div class="box-2" style="position: absolute;"></div>
      <div class="box-1" style="opacity: 0.8;"></div>
    </div>
    
  • 伸缩盒没有创建层叠上下文,伸缩盒中 z-index 值不为 auto 的 flex 项目才会创建层叠上下文。换句话说,使用这种方式创建层叠上下文需要同时满足两个条件:

    1. 父元素设置了display: flex / display: inline-flex
    2. 自身 z-index 属性值不为 auto;
    <style>
    .child {
      position: relative;
      z-index: -1;
    }
    </style>
    
    <div class="container">
      <div class="item">
        <div class="child"></div>
      </div>
    </div>
    <div class="container" style="display: inline-flex;">
      <div class="item" style="z-index: 1;">
        <div class="child"></div>
      </div>
    </div>
    <div class="container" style="display: inline-flex;">
      <div class="item" style="z-index: -1;">
        <div class="child"></div>
      </div>
    </div>
    

    左边实例,ContainerItem 元素都没有创建层叠上下文,这两者与 Child 元素都处于同一个层叠上下文中,按照层叠顺序渲染;

    中间实例,Container 元素设置 display: inline-flex 成为 Flex 容器,Item 元素设置 z-index: 1,则 Item 元素创建了层叠上下文,所以即使 Child 元素的 z-index 值为负数,却依然层叠在 Item 元素之上;

    右边实例,把 Item 元素的 z-index 值改为 -1,由于 Container 元素作为 Flex 容器是没有创建层叠上下文的,所以 Item 元素会层叠在 Container 元素之下。

固定定位(position: fixed)的特殊性

我们发现,单纯是绝对/相对定位元素是无法创建一个层叠上下文的,需要同时满足 z-index 值不为 auto 的条件。然而,固定定位元素就不需要满足这个条件,显得尤为特殊。

  • 固定定位元素无需满足 z-index 值不为 auto 的条件就可以创建层叠上下文;同样地,设置 z-index: auto 并不能撤销固定定位元素所创建的层叠上下文。

    <style>
    .fixed {
      position: fixed;
      z-index: auto;
    }
    .child {
      position: relative;
      z-index: -1;
    }
    </style>
    <div class="fixed">
      <div class="child"></div>
    </div>
    
  • 正常情况下,固定定位是相对于浏览器视窗(viewport)进行定位的,但是当其祖先元素中存在符合以下任意一个条件的元素时,固定定位元素会相对于该元素进行定位:

    1、transform 属性值不为 none;
    2、transform-style: preserve-3d
    3、perspective 属性值不为 none
    4、will-change 属性 指定了上面 3 个 CSS 属性中的任意一个

    然而,这种表现在不同的浏览器中有差异,譬如在 Safari 中只有上述第 1 点会对固定定位元素产生影响。

建议

通过对层叠上下文和层叠顺序的了解,我们知道,要控制元素间的层叠关系除了使用 z-index 属性外还有很多途径,而且比使用 z-index 要优雅得多。滥用 z-index 往往只会把层叠关系复杂化,造成代码难以维护。

友情链接

学习完 CSS 层叠上下文,如果对 BFC(块格式化上下文)、IFC(行内格式化上下文)等相关概念有兴趣的,可以学习这篇文章:视觉格式化模型 | MDN

文中涉及到的完整实例代码已放在 Github 上,欢迎大家交流。

Reference

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,425评论 4 361
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,058评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,186评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,848评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,249评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,554评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,830评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,536评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,239评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,505评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,004评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,346评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,999评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,060评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,821评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,574评论 2 271
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,480评论 2 267

推荐阅读更多精彩内容