Open toFrankie opened 1 year ago
在 HTML 页面中,我们通常会使用 margin、float、offset 等 CSS 属性控制元素在 X 轴和 Y 轴页面中的位置,另外会使用 z-index 和 transform 来控制元素在 Z 轴的排列顺序。
margin
float
offset
z-index
transform
因此,我们可以得知 HTML 页面其实是一个三维的结构。
了解以下这几个词:
层叠上下文是一个比较抽象的概念。当一个元素拥有了一个“三维”的表现,即在 Z 轴上有一定的顺序,那么我们称该元素有一个层叠上下文。
有了层叠上下文之后,需要一个叫做层叠等级(层叠水平)的家伙来决定同一个层叠上下文中元素在 Z 轴上的显示顺序。
层叠顺序是一种用于描述层叠等级的特定规则。
如何区分?
层叠上下文和层叠等级是概念,而层叠顺序是规则。通俗地讲,前两者就像是公司和领导,负责画饼和制定计划,而后者则是打工人,负责具体实施(就是搬砖)。
本文中所提到的“层叠元素”和“定位元素”均按以下描述进行约定,有特殊说明除外。
position
static
层叠元素有以下特性:
以下几种方式,都会创建层叠上下文:
根层叠上下文 页面的根元素 <html> 自带一个层叠上下文。我们知道,绝对定位元素通过 top/right/bottom/left 来定位时,若没有其他定位元素限制,会相对浏览器窗口定位。其中缘由,就是因为有根层叠上下文的原因。
<html>
top/right/bottom/left
定位元素层叠上下文(传统) 当元素的 position 不为 static,z-index 不为 auto 时,该元素就会创建层叠上下文(但有些特例,下文会提到)。
auto
CSS3层叠上下文(新时代) 在 CSS3 中新增了很多新属性,有一些会创建层叠上下文。
flex
display:flex|inline-flex
opacity
1
none
mix-blend-mode
normal
filter
isolation
isolate
will-change
-webkit-overflow-scrolling
touch
特例说明 在远古神兽 IE5/6/7 浏览器下,z-index: auto 会创建层叠上下文。至于这是 Bug 还是微软故意为之,就不去考究了,反正也接触不到这些远古神兽了。在现代浏览器(包括 IE8 及以上)中,z-index: auto 都不会创建层叠上下文。 在过去 position: relative | absolute | fixed 都需要配合 z-index(数值)才会创建新的层叠上下文。但是不知道什么时候起,Chrome、Firefox、Safari、Edge 等浏览器,position: fixed | sticky 元素天然层叠上下文,无需 z-index 为数值。(2022.01 亲测)
特例说明
z-index: auto
position: relative | absolute | fixed
position: fixed | sticky
一旦普通元素具有了层叠上下文,其层叠顺序就会变高。
分为两种情况:
如果层叠元素不依赖 z-index,那么其层叠顺序看成与 z-index: auto 一致的 z-index: 0 级别。 如果层叠元素依赖 z-index,那么其层叠顺序由 z-index 决定。
z-index: 0
*张鑫旭大佬原图出处,请看这里。
Q:为什么定位元素会层叠在普通元素的上面?
当一个元素成为了定位元素,z-index 会自动生效,取其默认值 auto。根据层叠顺序图,我们可以看到 z-index: auto 的层叠顺序比 block、float、inline/inline-block 都要高。
block
inline/inline-block
当元素发生层叠时,其覆盖关系遵循以下两个准则:
谁大谁上:在同一层叠上下文中,层叠等级较大的会覆盖较小的。 后来居上:在同一层叠上下文中,若层叠等级(顺序)相同时,在 DOM 文档流中处于后面的元素会覆盖前面的元素。
请注意,上面两条准则的前提都是“处于同一层叠上下文中”,那是因为在不同层叠上下文的比较是没有意义的。
接下来,介绍一些常见的层叠上下文、层叠顺序的案例,帮助进一步理解。
它们取值如下:
position: static(default) | relative | absolute | fixed | sticky
z-index: auto(default) | <integer> | inherit
当 position 不为 static 时,z-index 才会生效。
其中 z-index 属性值如下:
0
*其实还有一些全局值(如 initial、unset),但不重要,故忽略。
initial
unset
请注意,在 CSS3 里当父元素设为 display: flex | inline-flex,子元素使用 z-index 时,也会使得子元素创建新的层叠上下文(请注意:创建新的层叠上下文是子元素,而不是父元素哦)。
display: flex | inline-flex
换句话说,z-index 不再是定位元素独享,它还可以与 flex 搞在一起做点什么。
基础示例一 👉 源码及演示
基础示例二 👉 源码及演示
以上两个示例都很基础,不多说了...
若 position 为 static 时,top、right、bottom、left 和 z-index 属性均无效。 当 z-index 生效时,层叠顺序表现为:-integer < auto/0 < +integer。 仅层叠顺序而言,z-index: auto 和 z-index: 0 表现是一致,都是 0 级别。但两者在层叠上下文领域有着根本性的差异,前者不会创建新的层叠上下文,而后者会创建新的层叠上下文。 即使不显式设置 z-index,定位元素的层叠顺序也会比普通元素高。
top
right
bottom
left
-integer < auto/0 < +integer
👉 源码及演示
从结果上看,可以知道:
层叠顺序 z-index: -1 < z-index: auto | z-index: 0 < z-index: 1,且 z-index: auto 和 z-index: 0 层叠顺序均为 0 级别。 当 z-index: auto 和 z-index: 0 的定位元素同时存在,发生层叠时,在 DOM 流中处于后面的元素会覆盖前面的元素(同一层叠上下文中)。
z-index: -1 < z-index: auto | z-index: 0 < z-index: 1
请记住:处于同一层叠上下文中,遵循谁大谁上、后来居上的准则。
图中示例的区别在于 inner 盒子外层的 outer 盒子,一个设为 z-index: auto,另一个设为 z-index: 0。
inner
outer
假设 z-index: auto 会使得元素本身创建新的层叠上下文,那么 inner1 和 inner2 对应的参照物分别是 outer1 和 outer2,此时 inner1 中的 z-index 值,其实是没有意义的,无论它的值是正数、负数、还是 0 或 auto,它总会显示在 outer1 上面(inner2 同理)。同时,由于 outer1 和 outer2 的层叠顺序是一致的,outer2 在 DOM 流后面,属于“后来居上”,因此 outer2 会覆盖 outer1。
inner1
inner2
outer1
outer2
倘若假设成立,蓝色块应覆盖在绿色块上面(效果应如示例二相同),可事实并非如此。因此,我们可以得出结论:z-index: auto 并不会创建新的层叠上下文。
给你们看看远古神兽 IE5/6/7 浏览器下,z-index: auto 创建层叠上下文的效果。
因此,较为严谨的说法是:在现代浏览器(包括 IE8 及以上浏览器)中,z-index: auto 不会创建层叠上下文。
究其原因,其实很简单,前面也提到过。比较层叠顺序时,应在同一层叠上文中进行对比,否则是无意义的。
在示例一,red 盒子和 green 盒子处于同一层叠上下文(即根层叠上下文)中,且 z-index: -1 层叠顺序较低,所以可以看到 red 盒子覆盖了 green 盒子。
red
green
z-index: -1
在示例二,由于 red 盒子的 z-index: 0 是使得其本身创建了新的层叠上下文,所以 green 永远不会穿越 red 盒子。此时,即使 green 盒子的 z-index 为 -9999 也总会显示在 red 盒子上层。
-9999
在示例三中,red 盒子内多了一个 blue 盒子,并设为 z-index: 1。由于 blue 盒子和 green 盒子同属于 red 盒子创建的层叠上下文,因而,此时 z-index 就有了比较的意义了。所以,我们可以看到 green 盒子处于 blue 盒子下方,且不会穿过 red 盒子。
blue
z-index: 1
再看另外一个示例 👉 源码及演示
它的原因是一样的,因为除了 z-index 会创建层叠上下文之外,CSS3 中的 transform 也会创建新的层叠上下文哦!
以上两个都是常见的 z-index “失效”的场景,根本原因还是,有的小伙伴不知道 transform 也会创建层叠上下文。
当遇到 z-index 失效时,Check List 如下: 检查当前元素是否为定位元素,或其父级元素是否有 display: flex | inline-flex; 检查它们是否处于同一层叠上下文; 若前面条件都确认无疑,那说明有一些你不知道的 CSS 属性会创建层叠上下文(此时应去翻阅文档); 否则,就是这垃圾浏览器没有遵循标准,偷偷地创建了新的层叠上下文(这种情况应该几乎不会出现,毕竟微软都放弃 IE 转投 Chromium 了)。
当遇到 z-index 失效时,Check List 如下:
于 2022.01 亲测,Chrome 97、Safari 15.2、Firefox 97、Microsoft Edge 97 浏览器下面,表现均如上图。由于 position: fixed | sticky 自带层叠上下文,因此图中示例三、示例四蓝色块会覆盖绿色块。
在 CSS3 中有几个与动画相关的属性:transform、transition、animation 分别对应变换、过渡、动画。虽意义相近,但具体角色不一。它们的取值如下:
transition
animation
.selector { transform: transform-function | none; transition: property duration timing-function delay; animation: name duration timing-function delay iteration-count direction fill-mode play-state; }
本文重点并非谈谈它们的区别,我们接着看 transform。与之相关的 CSS 属性有这些:
.selector { /* 设置元素变形方式,如旋转、缩放、倾斜、平移等 */ transform: transform-function | none; /* 更改一个元素变形的原点 */ transform-origin: <length> | <percentage> | center | left | right | top | bottom; /* 定义与 transform、transform-origin 这两个属性有关联的布局框 */ transform-box: content-box | border-box | fill-box | stroke-box | view-box; /* 设置元素的子元素是位于平面中,还是 3D 空间中 */ transform-style: flat | preserve-3d; }
它还有兼容性属性:
.selector { -webkit-transform: transform-function | none; -moz-transform: transform-function | none; -ms-transform: transform-function | none; -o-transform: transform-function | none; transform: transform-function | none; }
其中 transform-function 的变换函数有以下这些:
transform-function
perspective()
matrix()
matrix3d()
skew()
skewX()
skewY()
scale()
scaleX()
scaleY()
scaleZ()
scale3d()
rotate()
rotateX()
rotateY()
rotateZ()
rotate3d()
translate()
translateX()
translateY()
translateZ()
translate3d()
我们知道,只要 transform 不为 none 时,它也会创建层叠上下文。还有 translateZ()、perspective() 两个变换函数,可以实现 z-index 类似的效果。translateZ(tz) 就是 translate3d(0, 0, tz) 简写形式。
translateZ(tz)
translate3d(0, 0, tz)
从字面上看,我们可以很容易知道,translateZ 的变换是属于 3D 空间的。
translateZ
先看个简单示例:
为什么红色块的 translateZ 值更大,但却不是在更上面呢?
原因很简单,就是因为 transform-style 的默认值是 flat,即处于 2D 平面中。如果选择平面,元素的子元素将不会有 3D 的遮挡关系。
transform-style
flat
它的取值有:
transform-style: flat | preserve-3d
只要将其添加一个 transform-style: preserve-3d 的父元素,就能得到预期结果:红色覆盖在绿色上面。preserve-3d 使得该元素的子元素应位于 3D 空间中。
transform-style: preserve-3d
preserve-3d
再看示例 👉 源码及演示
以上示例多组对比,其实还是为了说明:在不同层叠上下文中比较层叠高低是没有意义的。
看示例 👉 源码及演示
我们来看下 iPhone 微信浏览器下,表现如何:
看到差异了吗?
原因是,苹果旗下的 Safari 浏览器在使用 3D 变换时,会忽略 z-index 的作用。
详情戳这里:Safari 3D transform 变换 z-index 层级渲染异常的研究
个人建议
尽量不要同时使用 translateZ() 和 z-index,iOS 与 Android 下表现有差异; 在涉及 3D 变换时,应采用 transform-style: preserve-3d、transform: translateZ()、transform: perspective 的方案,而不是 z-index;
transform: translateZ()
transform: perspective
未完待续...
一、概念
在 HTML 页面中,我们通常会使用
margin
、float
、offset
等 CSS 属性控制元素在 X 轴和 Y 轴页面中的位置,另外会使用z-index
和transform
来控制元素在 Z 轴的排列顺序。了解以下这几个词:
层叠上下文是一个比较抽象的概念。当一个元素拥有了一个“三维”的表现,即在 Z 轴上有一定的顺序,那么我们称该元素有一个层叠上下文。
有了层叠上下文之后,需要一个叫做层叠等级(层叠水平)的家伙来决定同一个层叠上下文中元素在 Z 轴上的显示顺序。
层叠顺序是一种用于描述层叠等级的特定规则。
如何区分?
层叠上下文和层叠等级是概念,而层叠顺序是规则。通俗地讲,前两者就像是公司和领导,负责画饼和制定计划,而后者则是打工人,负责具体实施(就是搬砖)。
二、重点
本文术语约定
本文中所提到的“层叠元素”和“定位元素”均按以下描述进行约定,有特殊说明除外。
position
属性,且其值不为static
的元素。注意点
z-index
属性混为一谈,尽管z-index
在某些情况下会影响到层叠等级。层叠上下文的特性
层叠元素有以下特性:
创建层叠上下文
以下几种方式,都会创建层叠上下文:
根层叠上下文 页面的根元素
<html>
自带一个层叠上下文。我们知道,绝对定位元素通过top/right/bottom/left
来定位时,若没有其他定位元素限制,会相对浏览器窗口定位。其中缘由,就是因为有根层叠上下文的原因。定位元素层叠上下文(传统) 当元素的
position
不为static
,z-index
不为auto
时,该元素就会创建层叠上下文(但有些特例,下文会提到)。CSS3层叠上下文(新时代) 在 CSS3 中新增了很多新属性,有一些会创建层叠上下文。
z-index
值不为auto
的flex
项(父元素display:flex|inline-flex
);opacity
值不为1
;transform
值不为none
;mix-blend-mode
值不为normal
;filter
值不为none
;isolation
值为isolate
;will-change
指定的属性值为上面任意一个;-webkit-overflow-scrolling
设为touch
。层叠上下文与层叠顺序
一旦普通元素具有了层叠上下文,其层叠顺序就会变高。
分为两种情况:
*张鑫旭大佬原图出处,请看这里。
Q:为什么定位元素会层叠在普通元素的上面?
当一个元素成为了定位元素,
z-index
会自动生效,取其默认值auto
。根据层叠顺序图,我们可以看到z-index: auto
的层叠顺序比block
、float
、inline/inline-block
都要高。层叠准则
当元素发生层叠时,其覆盖关系遵循以下两个准则:
请注意,上面两条准则的前提都是“处于同一层叠上下文中”,那是因为在不同层叠上下文的比较是没有意义的。
接下来,介绍一些常见的层叠上下文、层叠顺序的案例,帮助进一步理解。
三、position 与 z-index
它们取值如下:
当
position
不为static
时,z-index
才会生效。其中
z-index
属性值如下:0
,且不会创建新的层叠上下文(默认值)。*其实还有一些全局值(如initial
、unset
),但不重要,故忽略。请注意,在 CSS3 里当父元素设为
display: flex | inline-flex
,子元素使用z-index
时,也会使得子元素创建新的层叠上下文(请注意:创建新的层叠上下文是子元素,而不是父元素哦)。换句话说,
z-index
不再是定位元素独享,它还可以与flex
搞在一起做点什么。基础示例一 👉 源码及演示
基础示例二 👉 源码及演示
以上两个示例都很基础,不多说了...
小结
验证 z-index: auto 和 z-index: 0 的层叠顺序是相同的
👉 源码及演示
从结果上看,可以知道:
请记住:处于同一层叠上下文中,遵循谁大谁上、后来居上的准则。
验证 z-index: auto 不会创建层叠上下文
👉 源码及演示
图中示例的区别在于
inner
盒子外层的outer
盒子,一个设为z-index: auto
,另一个设为z-index: 0
。假设
z-index: auto
会使得元素本身创建新的层叠上下文,那么inner1
和inner2
对应的参照物分别是outer1
和outer2
,此时inner1
中的z-index
值,其实是没有意义的,无论它的值是正数、负数、还是0
或auto
,它总会显示在outer1
上面(inner2
同理)。同时,由于outer1
和outer2
的层叠顺序是一致的,outer2
在 DOM 流后面,属于“后来居上”,因此outer2
会覆盖outer1
。倘若假设成立,蓝色块应覆盖在绿色块上面(效果应如示例二相同),可事实并非如此。因此,我们可以得出结论:
z-index: auto
并不会创建新的层叠上下文。给你们看看远古神兽 IE5/6/7 浏览器下,
z-index: auto
创建层叠上下文的效果。关于 z-index 负值时而有效,时而无效的问题
👉 源码及演示
究其原因,其实很简单,前面也提到过。比较层叠顺序时,应在同一层叠上文中进行对比,否则是无意义的。
在示例一,
red
盒子和green
盒子处于同一层叠上下文(即根层叠上下文)中,且z-index: -1
层叠顺序较低,所以可以看到red
盒子覆盖了green
盒子。在示例二,由于
red
盒子的z-index: 0
是使得其本身创建了新的层叠上下文,所以green
永远不会穿越red
盒子。此时,即使green
盒子的z-index
为-9999
也总会显示在red
盒子上层。在示例三中,
red
盒子内多了一个blue
盒子,并设为z-index: 1
。由于blue
盒子和green
盒子同属于red
盒子创建的层叠上下文,因而,此时z-index
就有了比较的意义了。所以,我们可以看到green
盒子处于blue
盒子下方,且不会穿过red
盒子。再看另外一个示例 👉 源码及演示
它的原因是一样的,因为除了
z-index
会创建层叠上下文之外,CSS3 中的transform
也会创建新的层叠上下文哦!以上两个都是常见的
z-index
“失效”的场景,根本原因还是,有的小伙伴不知道transform
也会创建层叠上下文。关于 position: fixed | sticky 自带层叠上下文
👉 源码及演示
于 2022.01 亲测,Chrome 97、Safari 15.2、Firefox 97、Microsoft Edge 97 浏览器下面,表现均如上图。由于
position: fixed | sticky
自带层叠上下文,因此图中示例三、示例四蓝色块会覆盖绿色块。四、transform
在 CSS3 中有几个与动画相关的属性:
transform
、transition
、animation
分别对应变换、过渡、动画。虽意义相近,但具体角色不一。它们的取值如下:本文重点并非谈谈它们的区别,我们接着看
transform
。与之相关的 CSS 属性有这些:它还有兼容性属性:
其中
transform-function
的变换函数有以下这些:perspective()
(查看示例)matrix()
、matrix3d()
(查看示例)skew()
、skewX()
、skewY()
(查看示例)scale()
、scaleX()
、scaleY()
、scaleZ()
、scale3d()
(查看示例)rotate()
、rotateX()
、rotateY()
、rotateZ()
、rotate3d()
(查看示例)translate()
、translateX()
、translateY()
、translateZ()
、translate3d()
(查看示例)我们知道,只要
transform
不为none
时,它也会创建层叠上下文。还有translateZ()
、perspective()
两个变换函数,可以实现z-index
类似的效果。translateZ(tz)
就是translate3d(0, 0, tz)
简写形式。transform-style 与 translateZ
从字面上看,我们可以很容易知道,
translateZ
的变换是属于 3D 空间的。先看个简单示例:
为什么红色块的
translateZ
值更大,但却不是在更上面呢?原因很简单,就是因为
transform-style
的默认值是flat
,即处于 2D 平面中。如果选择平面,元素的子元素将不会有 3D 的遮挡关系。它的取值有:
只要将其添加一个
transform-style: preserve-3d
的父元素,就能得到预期结果:红色覆盖在绿色上面。preserve-3d
使得该元素的子元素应位于 3D 空间中。再看示例 👉 源码及演示
以上示例多组对比,其实还是为了说明:在不同层叠上下文中比较层叠高低是没有意义的。
Transform 和 z-index 同时使用,会产生什么问题呢?
看示例 👉 源码及演示
我们来看下 iPhone 微信浏览器下,表现如何:
看到差异了吗?
原因是,苹果旗下的 Safari 浏览器在使用 3D 变换时,会忽略
z-index
的作用。个人建议
未完待续...
References