必须掌握的css之stacking context

742 阅读6分钟

层叠顺序(stacking level)与层叠上下文(stacking context)知多少?

z-index看上去其实很简单,根据z-index的大小决定层叠的优先级,实则深入进去,会发现内有乾坤。

看看下面这题,定一个两个divA和B,被包括在同一个父div标签下。HMTL结构如下:

<div class="container">
	<div class="inline-block">#divA display:inline-block</div>
	<div class="float">#divB float:left</div>
</div>

它们的css定义如下:

.container{
	position:relative;
	background:#ddd;
}
.container > div{
	width:200px;
	height:200px;
}
.float{
	float:left;
	background-color:#deeppink;
}
.inline-block{
	display:inline-block;
	background-color:yellowgreen;
	margin-left:-100px;
}

大概描述起来,意思就是拥有共同父级容器的两个DIV重叠在一起,是display:inline-block在上面,还是float:left在上面?

注意这里DOM的顺序,是先生成display:inline-block,再生成float:left。当然也可以把两个DOM顺序调转如下:

<div class="container">
	<div class="float">#divB float:left</div>
	<div class="inline-block">#divA display:inline-block</div>
</div>

会发现,无论顺序如何,始终是display:inline-blockdiv在上面。

原理分析:

这里其实涉及了所谓的层叠顺序(stacking level),有一张图可以很好的诠释:

运用上图的逻辑,上面的题目就迎刃而解了,inline-blockstacking levelfloat要高,所以无论DOM的先后顺序都是堆叠在上面。

不过上面图示的说法有一些不准确,按照W3C官方的说法,准确的7层为:

  1. 形成堆叠上下文环境的元素的背景与边框
  2. 拥有负z-index的子堆叠上下文元素(负的越高堆叠层级越低)
  3. 正常流式布局,非inline-block,无position定位(static除外)的子元素
  4. position定位(static除外)的float浮动元素
  5. 正常流式布局,inline-block元素,无position定位(static除外)的子元素(包括display:table和display:inline)
  6. 拥有z-index:0的子堆叠上下文元素
  7. 拥有正z-index的子堆叠上下文元素(正的越高堆叠层级越高)

所以我们的两个div的比较是基于上面所列出来的4和5。5的stacking level更高,所以叠得更高。

不过!不过!不过!重点来了,请注意,上面的比较是基于两个div都没有形成堆叠上下文这个为基础的。下面我们修改一下题目,给两个div,增加一个opacity

.container{
	position:relative;
	background:#ddd;
}
.container > div{
	width:200px;
	height:200px;
	opacity:0.9; //注意这里,增加一个opacity
}
.float{
	float:left;
	background-color:#deeppink;
}
.inline-block{
	display:inline-block;
	background-color:yellowgreen;
	margin-left:-100px;
}

会看到,inline-blockdiv不再一定叠在floatdiv之上,而是和HTML代码中DOM的堆放顺序有关,后添加的div会叠在先添加的div之上。

这里的关键点在于,添加的opacity:0.9这个让两个div都生成了stacking context(堆叠上下文)的概念。此时,要对两者进行层叠排列,就需要z-index,z-index越高的层叠层级越高。

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

那么,如何触发一个元素形成堆叠上下文?方法如下:摘自MDN:

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

所以,上面我们给两个div添加opacity属性的目的就是为了形成stacking context。也就是说添加opacity替换成上面列出来这些属性都是可以达到同样的效果。

在层叠上下文中,其子元素同样也按照上面解释的规则进行层叠。特别值得一提的是,其子元素的z-index值只在父级层叠上下文中有意义。意思就是父元素的z-index低于父元素另一个同级元素,子元素z-index再高也没用。

不受控制的position:fixed

大家都知道,position:fixed在日常的页面布局中非常有用,在许多布局中起到了关键作用。它的作用是:position:fixed的元素将相对于屏幕视口(viewport)的位置来指定其位置。并且元素的位置在屏幕滚动时不会改变。但是,在许多特定的场合,制定了position:fixed的元素却无法相对于屏幕视口进行定位,这是为何呢?

失效的position:fixed

在许多情况下,position:fixed将会失效。MDN用一句话概括了这种情况:

当元素祖先的transform属性非none时,定位容器由视口改为该祖先。

what!还有这种操作?可能部分同学还没get到上面这句话的意思,通俗的讲就是指定了position:fixed的元素,如果其祖先元素存在非none的transform值,那么该元素将相对于设定了transform的祖先元素进行定位。

这个问题,就牵涉到了上面提到的Stacking Context,解释上面失效的原因分为两步:

  1. 任何非none的transform值都会导致一个层叠上下文(Stacking Context)和包含块(Containing Block)的创建
  2. 由于层叠上下文的创建,该元素会影响其子元素的固定定位。设置了position:fixed的子元素将不会基于viewport定位,而是基于这个父元素

一探position:fixed失效的最终原因

不是所有能够生成层叠上下文的元素都会使得position:fixed失效,但也不止transform会使position:fixed失效。

所以,MDN关于position:fixed的补充描述不够完善。下述3种方式目前都会使得position:fixed定位的基准元素改变:

  1. transform属性值不为none的元素
  2. perspective值不为none的元素
  3. will-change中指定了任意CSS属性

但是不同内核有不同表现,所以当遇到position:fixed定位基准元素改变时,根据需要兼容适配的浏览器做出调整,不能一概而论