CSS 的层叠规则
所谓“层叠规则”,指的是当网页中的元素发生层叠时的表现规则。
在现实世界,凡事都有个先后顺序,凡物都有个论资排辈。例如,食堂排队打饭,讲求先到先得。
说到层叠,很多人第一反应就是 z-index 属性,人如其名,“z 轴顺序”明摆着就是和层叠规则有关。
z-index 属性只有和定位元素(position 不为 static 的元素)在一起的时候才有作用,可以是正数也可以是负数。理论上说,数值越大层级越高,但实际上其规则要复杂很多,这个后面会深入介绍。要知道,网页中绝大部分元素是非定位元素,并且影响层叠顺序的属性远不止 z-index 一个,因此大家千万不要以为 z-index 属性就可以代表 CSS 世界的层叠规则,实际上 z-index 只是 CSS 层叠规则中的一叶小舟,CSS 层叠规则的体量要比大家想象的要大得多。
1. 层叠上下文和层叠水平
1.1 什么是层叠上下文
层叠上下文,英文称作 stacking context,是 HTML 中的一个三维的概念。如果一个元素含有层叠上下文,我们可以理解为这个元素在 z 轴上就“高人一等”。
这里出现了一个名词—z 轴,它指的是什么呢?其表示的是用户与显示器之间这条看不见的垂直线。
层叠上下文是一个概念,跟“块状格式化上下文” (BFC)类似。然而,概念这个东西是比较虚、比较抽象的,要想轻松理解,我们需要将其具象化。
怎么个具象化法呢?我们可以把层叠上下文理解为一种“层叠结界”,自成一个小世界。这个小世界中可能有其他的“层叠结界”,而自身也可能处于其他“层叠结界”中。
1.2 什么是层叠水平
再来说说“层叠水平”。层叠水平,英文称作 stacking level,决定了同一个层叠上下文中元素在 z 轴上的显示顺序。页面中的每个元素都是独立的个体,它们一定是会有一个类似的排名顺序的存在。而这个排名顺序就是这里所说的“层叠水平”。
显而易见,所有的元素都有层叠水平,包括层叠上下文元素,也包括普通元素。然而,对普通元素的层叠水平探讨只局限在当前层叠上下文元素中。为什么呢?因为不如此就没有意义。 层叠上下文本身就是一个强力的“层叠结界”,而普通元素的层叠水平是无法突破这个结界和结界外的元素去较量层叠水平的。
需要注意的是,大家千万不要把层叠水平和 CSS 的 z-index 属性混为一谈。尽管某些情况下 z-index 确实可以影响层叠水平,但是只限于定位元素以及 flex 盒子的孩子元素;而层叠水平所有的元素都存在。
2. 元素的层叠顺序
再来说说层叠顺序。层叠顺序,英文称作 stacking order,表示元素发生层叠时有着特定的垂直显示顺序。注意,这里跟上面两个不一样,上面的“层叠上下文”和“层叠水平”是概念,而这里的“层叠顺序”是规则。
在 CSS3 之前,层叠顺序规则如下:
- 位于最下面的
background/border 特指层叠上下文元素的边框和背景色。每一个层叠顺序规则仅适用于当前层叠上下文元素的小世界。 inline 水平盒子指的是包括 inline/inline-block/inline-table 元素的“层叠顺序”,它们都是同等级别的。- 单纯从层叠水平上看,实际上
z-index:0 和 z-index:auto 是可以看成是一样的。
那么,为什么内联元素的层叠顺序要比浮动元素和块状元素都高?
background/border 为装饰属性,浮动和块状元素一般用作布局,而内联元素都是内容。网页中最重要的是什么?当然是内容了!尤其是 CSS 是为更好的图文展示而设计的,因此,一定要让内容的层叠顺序相当高,这样当发生层叠时,重要的文字、图片内容才可以优先显示在屏幕上。
3. 层叠准则
当元素发生层叠的时候,其覆盖关系遵循下面两条准则:
- 谁大谁上:当具有明显的层叠水平标识的时候,如生效的
z-index 属性值,在同一 个层叠上下文领域,层叠水平值大的那一个覆盖小的那一个。 - 后来居上:当元素的层叠水平一致、层叠顺序相同的时候,在 DOM 流中处于后面的元素会覆盖前面的元素。
4. 深入了解层叠上下文
4.1 层叠上下文的特性
层叠上下文元素有如下特性。
-
层叠上下文的层叠水平要比普通元素高(原因后面会说明)。 -
层叠上下文可以阻断元素的混合模式 -
层叠上下文可以嵌套,内部层叠上下文及其所有子元素均受制于外部的“层叠上下文”。 -
每个层叠上下文和兄弟元素独立,也就是说,当进行层叠变化或渲染的时候,只需要考虑后代元素。 -
每个层叠上下文是自成体系的,当元素发生层叠的时候,整个元素被认为是在父层叠上下文的层叠顺序中。
4.2 层叠上下文的创建
和块状格式化上下文一样,层叠上下文也基本上是由一些特定的 CSS 属性创建的。主要分为以下三种情况。
- 页面根元素天生具有层叠上下文,称为根层叠上下文。
z-index 值为数值的定位元素的传统层叠上下文。- 其他 CSS3 属性。
下面我们来分别了解它们。
4.2.1 根层叠上下文
根层叠上下文指的是页面根元素,可以看成是元素。因此,页面中所有的元素一 定处于至少一个“层叠结界”中。
4.2.2 定位元素与传统层叠上下文
对于 position 值为 relative/absolute 以及 Firefox/IE 浏览器(不包括 Chrome 浏览器)下含有 position:fixed 声明的定位元素,当其 z-index 值不是 auto 的时候,会创建层叠上下文。
例如下面这个栗子。
<div style="position:relative; z-index:auto;">
<img src="1.jpg" style="position:absolute; z-index:2;">
</div>
<div style="position:relative; z-index:auto;">
<img src="2.jpg" style="position:relative; z-index:1;">
</div>
1.jpg 会显示在2.jpg 之上。毕竟1.jpg 的 z-index 值是 2,而2.jpg 的 z-index 是 1。
下面我们对父级简单调整一下,把 z-index:auto 改成层叠水平一样高的 z-index:0 。代码如下:
<div style="position:relative; z-index:0;">
<img src="1.jpg" style="position:absolute; z-index:2;">
</div>
<div style="position:relative; z-index:0;">
<img src="2.jpg" style="position:relative; z-index:1;">
</div>
结果会发现覆盖关系居然反过来了,此时2.jpg 覆盖在了1.jpg 之上。
为什么小小的改变会产生相反的结果呢?差别就在于,z-index: auto 所在的<div> 元素是一个普通定位元素,于是,里面的两个<img> 元素的层叠比较就不受父级的影响,两者直接套用层叠准则。这里,两个<img> 元素有着明显不一的 z-index 值,因此遵循谁大谁上的准则,于是,z-index 为 2 的那个1.jpg 就显示在 z-index 为 1 的2.jpg 上面了。
而 z-index 一旦变成数值,哪怕是 0,就会创建一个层叠上下文。此时,层叠规则就发生了变化。层叠上下文的特性里面最后一条是自成体系。两个<img> 元素的层叠顺序比较变成了优先比较其父级层叠上下文元素的层叠顺序。这里,由于外面的两个<div> 元素都是 z-index:0 ,两者层叠顺序一样大,此时遵循层叠准则的另外一个准则后来居上,根据在 DOM 文档流中的位置决定谁在上面,于是,位于后面的2.jpg 就自然而然显示在1.jpg 上面了。对,没错,<img> 元素上的 z-index 没起作用!
有时候,我们在网页重构的时候会发现 z-index 嵌套错乱,这时要看看是不是受父级的层叠上下文元素干扰了,可能就豁然开朗了。
再提一下 position:fixed 。在过去,position:fixed 和 relative/absolute 在层叠上下文这一块是一样的,都是需要 z-index 为数值才行。但是,不知道什么时候起, Chrome 等 WebKit 内核浏览器下,position:fixed 元素天然层叠上下文元素,无须 z-index 为数值。
4.2.3 CSS3 与新时代的层叠上下文
CSS3的出现除了带来了新属性,还对过去的很多规则发出了挑战,其中对层叠上 下文规则的影响显得特别突出。
- 元素为
flex 布局元素(父元素 display:flex|inline-flex ),同时 z-index 值不是 auto 。 - 元素的
opacity 值不是 1。 - 元素的
transform 值不是 none 。 - 元素的
filter 值不是 none 。
还有很多,这里遍不一一列举了。
4.3 层叠上下文与层叠顺序
一旦普通元素具有了层叠上下文,其层叠顺序就会变高。那它的层叠顺序究竟在哪个位置、哪个级别呢?
这里需要分两种情况讨论:
- 如果层叠上下文元素不依赖
z-index 数值,则其层叠顺序是 z-index:auto ,可看成 z:index:0 级别; - 如果层叠上下文元素依赖
z-index 数值,则其层叠顺序由 z-index 值决定。 我们上面提供的层叠顺序图实际上还缺少其他重要信息。如下图所示。
这下大家应该知道为什么定位元素会层叠在普通元素的上面了吧?其根本原因就是:元素一旦成为定位元素,其 z-index 就会自动生效,此时其 z-index 就是默认的 auto ,也就是0级别,根据上面的层叠顺序表,就会覆盖inline 或block 或float 元素。而不支持z-index 的层叠上下文元素天然是 z-index:auto 级别,也就意味着,层叠上下文元素和定位元素是 一个层叠顺序的,于是当它们发生层叠的时候,遵循的是后来居上准则。
例如下面这个栗子。
<img src="1.jpg" style="position:relative">
<img src="2.jpg" style="transform:scale(1);">
这符合后来居上准则,2.jpg 覆盖在1.jpg 之上。
<img src="2.jpg" style="transform:scale(1);">
<img src="1.jpg" style="position:relative">
这同样符合后来居上准则,1.jpg 覆盖在2.jpg 之上。
你会发现,两者样式一模一样,只是在 DOM 流中的位置不一样,这导致它们的层叠表现不一样,后面的图片在前面的图片的上面显示。这就说明层叠上下文元素的层叠顺序就是 z-index:auto 级别。
5. z-index 负值
z-index 是支持负值的,例如 z-index:-1 或者 z-index:-99999 都是可以的。或许你会认为一个定位元素设置 z-index 负值,就会跑到页面的背 后,隐藏掉,看不到了。结果实际上是有时候确实隐藏了,但有时候又隐藏不掉。为什么会这样?
因为 z-index 负值的最终表现并不是单一的,而是与层叠上下文和层叠顺序密 切相关。前面展示的层叠顺序规则 7 阶图,其中最下面的 2 阶是理解 z-index 负值表现的关键。z-index 负值元素的层级是在层叠上下文元素上面、block 元素的下面,也就是 z-index 虽然名为负数层级,但依然无法突破当前层叠上下文所包裹的小世界。
例如下面这个栗子。
<div class="box">
<img src="1.jpg">
</div>
.box {
background-color: blue;
}
.box > img {
position: relative;
z-index: -1;
right: -50px;
}
此时.box 是一个普普通通的元素,图片元素所在的层叠上下文元素一定是.box 的某个祖先元素。
本例中,图片是 z-index 负值元素,.box 是 block 元素,也就是图片应该在.box 元素的后面显示,因此,图片会被.box 元素的蓝色背景覆盖。
现在,我们给.box 元素加个样式,使其具有层叠上下文。很多 CSS 属性都可以,我们这 里就使用不影响视觉表现的 transform 属性示意如下:
.box {
background-color: blue;
transform: scale(1);
}
.box > img {
position: relative;
z-index: -1;
right: -50px;
}
CSS3 transform 可以让元素具有新的层叠上下文,于是,对照上图的层叠顺序规则,非常明显地标明了 z-index 负值在层叠上下文元素的背景色之上,也就是说,这里 z-index 是负值的图片元素应该在.box 元素的上面。
可以这么说,z-index 负值渲染的过程就是一个寻找第一个层叠上下文 元素的过程,然后层叠顺序止步于这个层叠上下文元素。那 z-index 负值在实际项目中有什么用呢?具体作用如下。
- 可访问性隐藏。
z-index 负值可以隐藏元素,只需要层叠上下文内的某一个父元素加个背景色就可以。它与 clip 隐藏相比的一个优势是,元素无须绝对定位,设置 position:relative 也可以隐藏,另一个优势是它对原来的布局以及元素的行为没有任何影 响,而 clip 隐藏会导致控件 focus 的焦点发生细微的变化,在特定条件下是有体验问题的。 它的不足之处就是不具有普遍适用性,需要其他元素配合进行隐藏。 - 定位在元素的后面。我们可以通过设置它来达到我们想要的元素显示顺序。
|