转载

z-index和transform

z-indextransform 是CSS中的属性,但很少同学将二者联系到一起,感觉他们八杆子打不上。事实真的是这样吗?如果你也不能确认,这篇文章就值得你花点时间阅读。因为阅读完了,你会有所收获的。

堆叠上下文(Stacking Context)

在开始今天的主题之前,先得回忆一下CSS中的Stacking Context(堆叠上下文)。因为只有了解清楚了这个概念,才能更好的了解下面的内容。

任何HTML文档默认的堆叠上下文都是 <html> 元素。因此,除非创建新的堆叠上下文。默认情况下,元素的堆叠顺序相对于页面内的其他元素。在一个未做堆叠顺序更换的页面中,其顺序就是根据HTML中的元素出现的先后顺序来决定,先出现的在底下,后出现的在顶部。用数字来表示的话是就 1,2,3,4,...,n 这样的顺序。比如下面的示例:

第二个 div 做了一个 margin-top-50px ,可以看到第二个 div 遮住了第一个 div 。那么怎么才能改变默认的堆叠顺序呢?

先把结论给大家抛出来, 在CSS中可以使用 z-indextransform 可以改变元素的堆叠顺序 。但也可能会导致一些奇怪的情况,比如具有较大的 z-index 的元素并不总是位于具有较低 z-index 元素的上方。比如,在一些情况之下,同时使用 z-indextransform 会让 z-index 失效等。

CSS中会产生新的层情况还有很多种:

  • 当一个元素位于HTML文档的最外层( <html> 元素)
  • 当一个元素被定位了并且拥有一个 z-index 值(不为 auto
  • 当一个元素被设置了 opacitytransforms , filters , css-regions , paged media 等属性
  • flex item ,也就是父元素的 display 设置了 flex 或者 inline-flex 值,早期的 box 值不行
  • grid item ,也就是父元素的 display 设置了 grid 或者 inline-grid
  • isolation:isolate
  • 元素的 mix-blend-mode 值不为 normal
  • 元素的 overflow-scrolling 值不为 touch
  • 元素的 filter 值不为 none
  • 元素的 perspective 值不为 none
  • 元素的 motion-path 值不为 none

三维空间

Web中的任何元素都存在于一个三维空间中,除了大家熟知的平面画布中的 x 轴和 y 轴之外,还有控制第三维度的 z 轴,如下图所示:

z-index和transform

在CSS中使用 margin , floatoffset 这些属性,可以控制元素在 x 轴和 y 轴上的表现。而 z 轴上的表现形式可以通过 z-indextransform 来控制。

如何控制z轴

前面也说了,控制 z 是通过 z-indextransform 来实现的。先简单的了解一下这两种控制 z 轴的方法。

通过 z-index 控制 z 轴,需要配合 position 属性,且 position 的属性值为 relativeabsolutefixedsticky 时。并且给 z-index 显式的设置数值,数值越大,其层级越高。简单点说,数值越高,元素越在顶上。

z-index和transform

transform 可以通过它的 translateZ() 来改变元素的层叠顺序,其值越大,越在顶层,离屏幕越近。不过通过 transform:translateZ() 改变元素 z 轴的层级,必须在元素的父元素中显示的设置 transform-style: preserve-3d 或者在 transform 中显示的设置 perspective() 。如下所示:

z-index和transform

$list: #f44336,#9c27b0,#03a9f4,#009688,#ff9800,#795548;
body {
  padding: 30px;
  transform-style: preserve-3d;

  div {
    width: 200px;
    height: 200px;
    margin: 10px auto;
    position: absolute;
    &:before {
      content: attr(data-order);
      display: inline-block;
      font-size: 3em;
      font-weight: bold;
      color: #fff;
      position: absolute;
      bottom: 0;
      right: 0;
    }

    @for $i from 1 through 6 {
      &:nth-child(#{$i}){
        background-color: nth($list, $i);
        transform: translate(20 * $i + px, 20 * $i + px) translateZ(-1 * $i + px);
      }
    }
  }
}

上面的示例可能还不能明显的说明 translateZ() 改变堆叠上下文 z 轴的顺序,因为上面的代码有 position 设置,那你要是觉得好奇,可以看下面这个示例:

示例左边的元素是没有设置 translateZ ,右边的元素设置了 translateZ

有关于 z-indextransform 更多的教程可以阅读下面这些文章:

  • z-index 的工作原理
  • 没人告诉你关于 z-index 的一些事
  • 你对 position 的了解程度有多少?
  • 十步图解CSS的 position
  • CSS3 3D Transform
  • CSS3 2D Transform
  • Transform-style和Perspective属性

z-index 和 translate3d

特别声明:接下来的内容挑选于 @凹凸实验室 的《 探究 transform 动画元素的 z-index 》一文。此文章详细讲解了 transformz-index 在一起使用将会发生的状况。

在一次需求中,需要做出三张卡牌走马灯式滚动的效果,由于在前面的一张卡牌需要挡住后面的卡牌,自然而然地就用 z-index 使前面的卡牌显示在最上面,配以 transform 动画让“走马灯”滚起来,在开发过程中,在 PC 侧 Chrome 中表现良好,在本人手机浏览器中也表现良好,最后测试时却发现,在微信客户端或 QQ 客户端中打开页面出现问题,“走马灯”滚动时,卡牌先通过 transform 就位后,才把 z-index 设置较大的卡牌置于上面,感觉上非常的不流畅。

z-index和transform

究其原因,发现这是某些浏览器的渲染规则,涉及到 stacking context 的概念, transform 的元素会创建新的 DOM,层级会在普通元素的上面,除了 transform ,还有哪些情况会创建新 stacking context呢?

下图是对 transformopacity 的测试结果:

z-index和transform

很明显,红色 div 都在绿色 div 上面了,说明真的有创建了个更高层级的 stacking context。再做进一步测试,我给两组的 div 都加了 position:relative;z-index:1; ,结果绿色的都在上面了,手机微信上也一样,这能不能说明 z-index 对层级的影响大于 transformopacity 呢。

至于 transform 变换的时候会让 z-index “临时失效”,其实并非 z-index 失效了,只是 z-index 被用在不同的 stacking context 上,而非在默认的 context 上同等地比较层级了。所以 DOM 在 transform 的工程中,DOM 处于一个新的 stacking context 里, z-index 也是相对于这个 stacking context 的,所以表现出来的实际是 stacking context 的层次,动画一结束,DOM 又回到默认的 context 里,这时的 z-index 才是在同一个 context 上的比较。

那该用什么方法来控制卡牌的层级,又能让动画流畅地表现呢,当然是 translate3d 中的 z-axis ,很多时候我们并不知道它是用来做什么的,平常用得最多的只是它的 x-axisy-axis ,不妨先看个例子:

.box1 {
    width:100px;
    height:100px;
    background:red;
    transform:perspective(100px) translate3d(0, 0, 100px);
}
.box2 {
    width:100px;
    height:100px;
    background:blue;
    transform:perspective(100px) translate3d(0, 0, 200px);
}

实际效果是,看不到它们,然后我们再设置 perspective201px ,这时可以很明显地看到, .box2 占据了整个屏幕,而 .box1 宽高约为 200px ,唯有设置 translate3d(0,0,0) 时,宽高才为 100px

现在可以来理解下 perspectivetranslate3d 的关系, perspective 可以比作镜头和 DOM 的距离,实际上设置多少都没影响,因为它通过跟 z-axis 上的数值比例来影响样式,它更像是一个刻度,而 translate3dz-axis 则表示了 DOM 和屏幕的距离。假定镜头跟屏幕的距离固定了, z-axis 越大,DOM 逐渐远离屏幕,靠近镜头,这时 DOM 看起来也就越大,当 z-axis 大于或等于 perspective 时,DOM元素已经在我们镜头的后面了,所以也就看不到它了。

z-index和transform

现在也就好理解为什么 perspectivetranslate3d 能够影响 DOM 的层级了,它们在屏幕和镜头之间的距离不同,所以就有了层次,移动端设备很好地表现了这个结论,但在 PC 的 Chrome 上测试则不然,我们仍需要 z-index 才会表现出我们需要的 层次关系。

transform变换z-index层级渲染异常

在一些浏览器或设备上,当 transformz-index 在一起使用时会发生异样,造成 z-index 失灵。至于为什么会失灵,以及如何解决,这里就不多讲了。如果您对这方面的感兴趣,可以看看 @张鑫旭 大师写得一篇文章《 Safari 3D transform变换z-index层级渲染异常的研究 》。

文章总结了两种解决方案:

  • 方法1 :父级,任意父级,非 body 级别,设置 overflow:hidden 可恢复和其他浏览器一样的渲染
  • 方法2 :以毒攻毒。也可以使用3D transform变换

至于怎么使用3D Transform,大家还是 移步看张大师是怎么分析的 。

何时使用Transform来实现z-index

在介绍 z-indextranslate3d 一节中,我们也了解到了,有时候设置 z-index 来控制 z 轴并不有效,张大师文章也提到过,它们在一起使用时,有时候会使用 z-index 失灵。其实还有一个现象,大家可能平时并没有注意到。

当你通过 z-index 配合伪元素 ::before 或者 ::after 时让其 z 轴在元素的底部,特别是碰到大的元素渲染(比如全屏背景图),会直接影响性能,特别是在移动端,会造成客户端闪退,也就是大家所说的Crash,给用户造成非常不好的体验。

缩合上面的几个现象(当然可能还有很多我自己没有发现的),我们可以抛弃 z-index 来控制 z 轴的顺序,而是直接通过 transform 中的 translateZ() 或者 translate3d() 来控制 z 轴的顺序。

总结

单独使用 z-index 或者 transform 中的 translateZtranslate3d() ,或许你都不会想到他们之间有这么多的故事,甚至更没有想到在实际业务中通过 transform 来替代 z-index 来控制元素的 z 轴的顺序。那么这篇文章介绍的就是这两者之间的故事,以及如何通过 transform 来控制元素 z 轴的顺序。如果文章讲解的有不对之处,或者你碰到过更奇葩的现象,以及相关的解决方案,欢迎在下面的评论中与我们一起分享。

z-index和transform

大漠

常用昵称“大漠”,W3CPlus创始人,目前就职于手淘。中国Drupal社区核心成员之一。对HTML5、CSS3和Sass等前端脚本语言有非常深入的认识和丰富的实践经验,尤其专注对CSS3的研究,是国内最早研究和使用CSS3技术的一批人。CSS3、Sass和Drupal中国布道者。2014年出版《 图解CSS3:核心技术与案例实战 》。

原文  http://www.w3cplus.com/css/3d-transform-and-z-index.html
正文到此结束
Loading...