z轴的学习规则
html 元素沿着相对其用户的一条虚构的 z 轴排开,层叠上下文就是对这些 html 元素的三维构想。
背景
在 z 轴上,存在层叠上下文,他们有着遮挡关系的先后顺序,我们可以称之为层叠顺序,学习层叠上下文主要就是学习层叠顺序。在学习前,我们先来了解背景,我们试想一下,页面上为什么需要有 z 轴的存在?
举个例子:
常见的“顶部固定不动,内容区域向上滚动”的效果,我们给顶部设置position: fixed
,内容区域设置overflow: auto
。假设我们的内容在第一层,那顶部就在第一层之上,它不随第一层的滚动而滚动,因为 z 轴上的每一层都是相互独立的,这样轻而易举的达到了我们想要的效果。
如果不存在 z 轴,他们处在同一层,有可能实现这种效果吗?答案是有。内容向上滚动的同时,滚了多少距离,我们的顶部就向下滚动多少距离,这也可以实现相同的效果。但是这个过程的计算过程巨复杂,它使不变的元素(顶部)也进行了重新的计算,那么 GUI 渲染线程会重新计算 render tree,然后重新绘制页面元素。这将导致我们的页面性能非常差。
再举个常见的例子,比如视差、canvas,也是同个道理,都是为了更少的计算,让不变的保持不变。而这背后的原理,就是为了迎合浏览器的规则,减少回流。
所以答案就是:z 轴的存在,其实是网页性能优化的一种手段。使不变的元素保持不变,减少回流。
层叠上下文
我们可以把它们看做成一个个独立的空间,它们在 z 轴上存在顺序上的层叠关系。
层叠上下文存在以下特性
- 层叠上下文可以包含在其他层叠上下文当中,形成一个嵌套层级
- 每个层叠上下文完全独立于其他兄弟元素
层叠等级
层叠等级,也可以叫层叠级别/层叠水平,在同一个层级上下文中,等级越高,越靠近用户。
- 普通元素的层叠等级由所处层叠上下文决定
- 在同一个层叠上下文中,等级越高,越靠近用户
- 不在同一个上下文中的层叠等级比较是没有意义的
用代码验证一下第三点:
1 | <div class="yellow"> |
1 | .yellow { |
.yellow
的层叠等级为 1,.red
的层叠等级为 2,.blue
为 999,.yellow
和.red
在同一个根层叠上下文下。
但是由图可见,.blue
在.red
下面,这是因为.yellow
的层叠等级比.red
小,也证明了不在同一个上下文中的比较是没有意义的。
创建层叠上下文
html
为根层叠上下文position
属性为absolute
或relative
并设置了z-index
属性为具体数值position
值为fixed
或sticky
的元素- css3 的一些属性(文章后面介绍)
层叠顺序
层叠规则,表示元素在发生层叠时,有着特定的层叠顺序。层叠上下文和层叠等级是概念,而层叠顺序是规则。
层叠上下文的 background/border < z-index 负值 < block 元素 < float 元素 < inline-block/inline < z-index: 0 / z-index: auto < z-index: 正值
DOM 顺序和层叠顺序
从图中可以看出,层叠低的在层级高的下面,就像往容器里面放东西一样。它其实迎合栈的层叠规则,先进后出。所以浏览器会优先渲染层叠等级低的元素,再渲染比它高的。
所以结合实际的应用,在编码时,我们应该自然而然的把层叠等级低的写在最前面,让渲染更流畅。
1 | <div style="z-index: -100">div1</div> |
如果层级高的在下面会怎样?看个例子
我们先往容器(html)里放了一个 div1,在放 div2,div3,假设 3 个 div 都是层叠元素,div1 层叠等级最高,并且在最下面
1 | <div style="z-index: 1">div1</div> |
以上代码,假设先渲染等级高的(div1),那渲染等级低的元素时,是不是还要把它上面的元素先拿出来。
万能公式
- 同一个层叠上下文,谁的层叠等级大,谁再上面。
- 不在同一个层叠上下文,找到它们的父级为兄弟节点,再比较。
- 当层叠等级和层叠顺序相同时,后来居上。
下面我们来验证一下层叠顺序和万能公式是否准确。
例子 1:z-index: 负值 < block 元素:
1 | <div class="box"> |
1 | .box { |
.parent 是个普通的块元素,.child 是带负值的 z-index 层叠上下文元素,.parent 在.child 上面,所以 z-index: 负值 < block 元素成立。
例子 2: 层叠上下文的 background/border < z-index 负值
继续上个例子,我们给.parent 添加 position: relative 和 z-index: 0,其余代码不变
1 | <div class="box"> |
1 | .box { |
这时候.parent 变成了带 background 的层叠上下文元素,.child 依然是带负值的 z-index 层叠上下文元素。网上的很多文章拿这个例子来验证 background/border < z-index 负值,其实是不正确的。.child 在.parent 上,是因为.child 嵌套在.parent 里面,所以.child 在上面。也就是说他们不在用一个层叠上下文之下,所以他们的比较是没有意义的。
关于 background/border < z-index 负值,我还没想到如何验证。
例子 3:block 元素 < float 元素 < inline-block/inline
1 | <div class="box"> |
1 | .box { |
.box 是块元素,img 是浮动元素,span 是 inline 元素,inline 在最上面,img 在中间,所以block 元素 < float 元素 < inline-block/inline成立。
例子 4:同一个层叠上下文,谁的层叠等级大
1 | <div class="box"> |
1 | .box1 { |
.box1 和 .box2 的 z-index 值为 auto,此时不是层叠上下文元素,两张图片都属于在根层叠上下文下,因为盲僧在剑圣上面,所以同一个层叠上下文,谁的层叠等级大成立
例子 5:层叠等级和层叠顺序相同时,后来居上
在例子 4 的基础上,我们把.box1 和.box2 的 z-index: auto 改为 z-index: 0
1 | <div class="box"> |
1 | .box1 { |
此时.box1 和.box2 都变成了层叠等级为 0 的层叠上下文元素,并且在同个层叠上下文下。剑圣在盲僧上面,所以层叠等级和层叠顺序相同时,后来居上成立。
css3 中的属性对层叠上下文的影响
介绍几个比较常用的,如下:
- 父元素的 display 属性值为 flex | inline-flex,子元素的 z-index 属性值不为 auto,子元素为层叠上下文元素
- 元素的 opacity 值不为 1
- 元素的 filter 属性值不是 none
- 元素的 transform 属性值不是 none
例子 6:父元素 display: flex | inline-flex 与层叠上下文
1 | <div class="box"> |
1 | .box { |
继续例子 6,我们把.parent 变成层叠上下文元素,
例子 7:层叠上下文的 background/border < z-index 负值
1 | <div class="box"> |
1 | .box { |
.child 跑到了上面,所以层叠上下文的 background/border < z-index 负值成立。
例子 8:元素的 opacity 值不为 1
1 | <div class="box"> |
1 | .box { |
因为 z-index: 负值 < 块元素,所以图片在蓝色背景下。然后我们给 div 添加 opacity: 0.5,其余不变
1 | <div class="box"> |
1 | .box { |
添加 opacity 后,div 变成了层叠上下文元素,图片跑到了上面,所以层叠上下文的 background/border < z-index 负值成立。
例子 9:元素的 filter 属性值不是 none
在例子 8 上,我们给 div 添加 filter: blur(5px),其余不变
1 | <div class="box"> |
1 | .box { |
同理,结论成立。
例子 10:元素的 transform 属性值不是 none
在例子 8 上,我们给 div 添加 transform: rotate(45deg),其余不变
1 | <div class="box"> |
1 | .box { |
同理,结论成立。
开发调试工具
可以用过 layers 来查看页面的层叠布局,检查遮挡关系
More tools -> Layers
总结
- z 轴的存在,其实是网页性能优化的一种手段。使不变的元素保持不变,减少回流。
- 因为浏览器优先渲染层叠等级低的元素,所以编码时,我们应该自然而然的把层叠等级低的元素写在前面,让渲染更为流畅。
- 查看层叠等级时,先查看是否在同个层叠上下文下,如果是,层叠等级越高越在上面,层叠等级和层叠顺序相同时,后来居上。如果不是,那么判断所处层叠上下文的等级。
- 避免滥用 z-index。现在我们知道 inline > float,那么我们只需要把元素变成行内元素即可覆盖在浮动元素之上。过渡使用定位元素会使页面变复杂。
- 定位或者 css3 属性 display: flex | inline-flex 下,z-index: auto 所在的元素是普通元素,z-index: 0 是层叠元素。