沐光

记录在前端之路的点点滴滴

层叠上下文

前言

在做项目布局回顾时,思考到了一个当初比较犹豫的问题「flex 布局能否产生层叠/堆叠上下文」。在网上也参考了国内外很多有价值的文章,此处小结一下,算是对层叠上下文这一部分做一次较为全面的总结。

核心概念

层叠上下文是什么

这里先引用 MDN 上的一段对层叠上下文的介绍:

层叠上下文是 HTML 元素的三维概念,这些 HTML 元素在一条假想的相对于面向(电脑屏幕的)视窗或者网页的用户的 z 轴上延伸,HTML 元素依据其自身属性按照优先级顺序占用层叠上下文的空间。

简单来说,电脑屏幕给我们展现的是一个平面,此对应直角坐标系的 X、Y 轴,而垂直与电脑屏幕的这个不可见的轴(相当于直角坐标系的 Z 轴),其上面遍布着不同的堆叠层,而此堆叠层的层叠顺序就是我们所谓的层叠上下文了。

如果有设计经验的,那么这个堆叠层可以理解为 PS 中的画布,默认只有最初的一层(页面中的 html 元素)

层叠上下文的堆叠顺序

这边借用一张图来描述一下堆叠顺序:

堆叠顺序

此处对图做简单说明:

  1. 层叠上下文部分:相当于该层叠区域的装饰,优先级最低,处于最底层
  2. block 块状水平盒子:层叠区最为基础的布局方式,主要目的是为了控制呈现的样式
  3. float 浮动盒子:float 默认的 display 属性就是 block ,只是脱离了文档流,其也是一种布局方式
  4. inline/inline-block 水平盒子:层叠区默认的内容呈现部分,承载页面大多数内容部分(涉及到页面内容的标签基本上是 inline ,或者是隐式行内盒)
  5. z-index + position(和部分 CSS3 属性):控制元素在其所在的层叠区 Z 轴上的顺序(默认 auto,相当于 0),有正负值

其中比较有趣的一点是,inline/inline-block 的层级顺序要高于 block 元素,具体的体现就在“文字环绕”式布局了。

其实这么排序也有其理由,我们知道,页面其主要目的是为了呈现内容的,而块级元素偏向于布局(设置宽高、内外边距、边框、行内元素的大多数配置等等),行内元素偏向于内容(与其它内容的间隔、字体大小等等),因此不难理解行内元素应该高于块级元素的层叠级别了。

此外,对于默认情况(什么都没有设置),后面的元素默认堆叠顺序会高于前面的元素,具体的体现为两个 button 按钮,如果将后者的 margin 值设为负值,其会覆盖掉先前的按钮。

注:在层叠上下文中,其子元素同样也按照上面解释的规则进行层叠。 特别值得一提的是,其子元素的 z-index 值只在父级层叠上下文中有意义。子级层叠上下文被自动视为父级层叠上下文的一个独立单元。

如何产生层叠上下文

此处还是引用 MDN 上的介绍,文档中的层叠上下文由满足以下任意一个条件的元素形成(未做说明的会影响子元素的 fix 布局):

  • 根元素 (HTML)
  • z-index 值不为 “auto” 的 绝对/相对定位(不影响子元素的 fix 布局)
  • z-index 值不为 “auto” 的 flex 项目 (flex item),即:父元素 display: flex|inline-flex
  • opacity 属性值小于 1 的元素(参考 the specification for opacity)(不影响子元素的 fix 布局)
  • transform 属性值不为 “none” 的元素
  • mix-blend-mode 属性值不为 “normal” 的元素(不影响子元素的 fix 布局)
  • filter 值不为 “none” 的元素
  • perspective 值不为 “none” 的元素
  • isolation 属性被设置为 “isolate” 的元素(不影响子元素的 fix 布局)
  • position: fixed(不影响子元素的 fix 布局)
  • will-change 中指定了任意 CSS 属性,即便你没有直接指定这些属性的值(参考这篇文章
  • -webkit-overflow-scrolling 属性被设置 “touch” 的元素(不影响子元素的 fix 布局)
  • (补充)transform-style 为 preserve-3d 的元素

当然,这里仅仅只是列举了能够形成层叠上下文的一些情况,而其中也有部分翻译的的不是很准确(参考张鑫旭的文章,后面做了一些要点描述)

层叠上下文的层级是 HTML 元素层级的一个层级,因为只有某些元素才会创建层叠上下文。可以这样说,没有创建自己的层叠上下文的元素 将被父层叠上下文包含。 —— MDN

详解层叠上下文的形成

z-index 不为 auto 的 flex 项目

此指的是父元素的 display 属性为 flex/inline-flex ,其子元素的 z-index 值不为 auto,此时该子元素会形成堆叠区。

transform 属性值不为 “none” 的元素

在做 position: fixed 定位时,其默认是基于页面窗口创建堆叠区(根元素有个最基础的堆叠区),然而父级元素的 transform 属性值非 none 时,其会生成一个堆叠层,使得内部的 fixed 布局不是以窗口为基本的堆叠层,而是此父级本身,从而产生很“酸爽”的 bug 体验。

不会影响 fix 布局的会生成自身层叠的例子

此部分对于 -webkit-overflow-scrolling 属性未能尝试成功,可能只能手机端显现(未测试)

会影响 fix 布局的层叠的例子

有人做了比较全的 demo,我 fork 了一份,删除了错误的 flex 布局例子,去掉不受影响的部分。

源码地址:传送门

总结

对于层叠上下文,当发生层叠的时候,其覆盖关系遵循两条黄金准则:

  1. 谁大谁上:当具有明显的层叠水平标示的时候,如具体的 z-index 值,在同一个层叠上下文领域,层叠水平值大的那一个覆盖小的那一个。
  2. 后来居上:当元素的层叠水平一致、层叠顺序相同的时候,在 DOM 流中处于后面的元素会覆盖前面的元素。

参考文章