前言:关于层叠上下文,笔者还没有去阅读更详细的 W3C 规范来了解更本质的原理(表打我,等我校招拿到 offer 了我就读好伐 T_T)。一直听说 CSS3 里的层叠上下文有新情况,但没找到很好的参考资料,故自己实战一把。鉴于笔者水平有限,如有任何遗漏或者错误,则恳请读者斧正。
1 CSS2.1 中规定的层叠上下文
Background and borders — of the element forming the stacking context. The lowest level in the stack.
Negative Z-Index — the stacking contexts of descendants elements with negative z-index.
Block Level Boxes — in-flow non-inline-level non-positioned descendants.
Floated Boxes — non-positioned floats
Inline Boxes — in-flow inline-level non-positioned descendants.
Z-index: 0 — positioned elements. These form new stacking contexts.
Positive Z-index — positioned elements. The highest level in the stack.
图文来源:What You May Not Know About the Z-Index Property
现在该笔者上场翻译了!在解释上面术语之前,需要阐明两个术语:“定位”指的是position
为relative
、absolute
、fixed
的元素,“非定位”则相反。
- 背景和边框:建立层叠上下文元素的背景和边框。层叠中的最低级
-
负 Z-index:
z-index
为负的后代元素建立的层叠上下文 - 块级盒:文档流内非行内级非定位后代元素
-
浮动盒:非定位浮动元素(笔者注:即排除了
position: relative
的浮动盒) - 行内盒:文档流内行内级非定位后代元素
- Z-index: 0:定位元素。这些元素建立了新层叠上下文(笔者注:不一定,详见后文)
-
正 Z-index:(
z-index
为正的)定位元素。层叠的最高等级
引文如上所表。但笔者提醒各位读者一点,“Z-index: 0
”级的定位元素不一定就会建立新的层叠上下文。因为:
CSS2.1:(z-index: auto)The stack level of the generated box in the current stacking context is 0. The box does not establish a new stacking context unless it is the root element.
当定位元素z-index: auto
,生成盒在当前层叠上下文中的层级为0
。但该盒不建立新的层叠上下文,除非是根元素。
规范是这样,但 IE6-7 有个 BUG,定位元素即便z-index: auto
照样创建层叠上下文。
以上是基于 CSS2.1 的层叠上下文介绍。下面要阐述的是在 CSS3 新环境下,层叠上下文的新变化。
2 CSS3 带来的变化
总的来说变化可以归为两点,我们之后一一探讨:
- CSS3 中许多属性会创建局部层叠上下文
- tranform属性改变绝对定位子元素的包含块
2.1 产生新层叠上下文的情况
以下情况会产生新的层叠上下文:
- 根元素(HTML)
- 绝对或相对定位且
z-index
值不为auto
- 一个伸缩项目
Flex Item
,且z-index
值不为auto
,即父元素display: flex|inline-flex
- 元素的
opacity
属性值小于1
- 元素的
transform
属性值不为none
- 元素的
mix-blend-mode
属性值不为normal
- 元素的
filter
属性值不为normal
- 元素的
isolation
属性值为isolate
position: fixed
-
will-change
中指定了上述任意属性,即便你没有直接定义这些属性元素的-webkit-overflow-scrolling
属性值为touch
以上列表译自:
Understanding CSS z-index—The stacking context,提醒广大读者,别看中文版,因为中文版并非实时跟进更新的,且翻译不太准确
2.2 提升层叠上下文中的层级
以上元素建立新层叠上下文的同时,也会提升元素自身所在层叠上下文中的层级。
我们以opacity
为例。来看下 CSS3 规范中的话:
If an element with opacity less than 1 is not positioned, implementations must paint the layer it creates, within its parent stacking context, at the same stacking order that would be used if it were a positioned element with ‘z-index: 0’ and ‘opacity: 1’. If an element with opacity less than 1 is positioned, the ‘z-index’ property applies as described in [CSS21], except that ‘auto’ is treated as ‘0’ since a new stacking context is always created.
如果元素opacity
小于1
且未定位,则必须在其父层叠上下文中,按其在定位了的z-index: 0
且opacity: 1
的情况中的层叠顺序绘制。如果opacity
小于1
且已定位,z-index
属性按 CSS2.1 应用,但auto
要视为0
,因为新的层叠上下文总是创建了的。
如下案例:
div {
width: 100px;
height: 100px;
}
#box1 {
position: absolute;
background: red;
top: 40px;
left: 40px;
}
#box2 {
background: blue;
}
<body>
<div id="box1"></div>
<div id="box2"></div>
<body>
以上 CSS 和 HTML 片段中,由于 box1 是绝对定位(层级为“Z-index: 0
”级),而 box2 是文档流内块级盒(层级为“块级盒”级),因此 box1 会层叠在 box2 之上。下面添加如下 CSS 规则:
#box2 {
opacity: .5;
}
这时候, box2 则会层叠在 box1 之上了。因为 box2 的opacity
为0.5
(小于 1),故视其为“Z-index: 0
”级,也就和 box1 同级了。同级情况下,按照二者在源代码中的顺序,居后的 box2 又重新占领高地了。
读者可以取下面规则之任意一条实验,都能达到同样效果:
#box2 {
transform: scale(1);
mix-blend-mode: difference;
isolation: isolate;
-webkit-filter: blur(5px);
}
2.3 transform 改变绝对定位子元素包含块
transform
除了建立新的局部层叠上下文外,还会干一件事:改变绝对定位子元素的包含块。须注意的是,固定定位也是绝对定位的一种。
什么是包含块?有时候一些盒子根据矩形盒计算自身定位和大小,此矩形盒即包含块。更多详情请阅读视觉格式化模型详述。
固定定位元素
固定定位元素的包含块由视口创建(如果读者了解视觉格式化模型详述的信息,也就知道这一点:在计算其“静态位置”的时候,则以初始化包含块作为其计算包含块)。现在我们看以下源代码:
div {
width: 100px;
height: 100px;
}
#fixed {
position: fixed;
width: 100%;
height: 100%;
top: 0;
left: 0;
background: blue;
}
#transform {
background: red;
padding: 20px;
}
<body>
<div id="transform">
<div id="fixed"></div>
</div>
</body>
这个时候,以视口为包含块进行定位和大小计算, fixed
将会铺满整个屏幕。
但现在,我们加上如下规则:
#transform {
transform: scale(1);
}
此时,fixed
的包含块不再是视口,而是transform
的内边距盒的边缘盒了。故此时fixed
的宽高均为140px
。
绝对定位元素
我们举一个例子:
#relative {
position: relative;
width: 100px;
height: 100px;
background: green;
}
#absolute {
position: absolute;
width: 100%;
height: 100%;
top: 0;
left: 0;
background: blue;
}
#transform {
background: red;
width: 50px;
height: 50px;
}
<div id="relative">
<div id="transform">
<div id="absolute"></div>
</div>
</div>
此时absolute
的包含块为relative
的内边距盒的边缘盒。由此absolute
的宽高均为100px
。然后我们添加如下规则:
#transform {
transform: scale(1);
}
由于transform
创建了局部层叠上下文,absolute
的包含块不再是 relative
而是transform
了,根据这一新的包含块,得新宽和高为50px
。