现在假设有个需求,要我们做三个方块,其中第三个方块要从初始位置往左滑动
先实现基础功能
我们先回顾一下简单的CSS动画是如何实现的:

此时的代码为:
<html> <head></head> <body> <div id="a" class="area"></div> <div id="b" class="area"></div> <div id="c" class="area"></div> </body></html><style> .area { width: 50px; height: 50px; margin-bottom: 10px; } #a { background-color: red; } #b { background-color: blue; } #c { background-color: green; animation: slidein 5s infinite; } @keyframes slidein { from { margin-left: 0; } to { margin-left: 100px; } }</style>优化思路
我们通过改变margin的方式让第三个方块动了起来,好了工作完成了,该下班回家了。。。才怪,PM嫌咱们做的动画在老设备上太卡,要提高流畅度,我们该怎么优化咧?
在着手优化之前,我们得先想明白,为什么这种方式是低效的。我们都知道,浏览器渲染会进行重排和重绘,一旦文档结构发生变更,便会牵一发而动全身,导致元素布局重新计算,如果元素之间关联紧密且变动频繁,便容易出现卡顿。故而我们在进行CSS修改的时候,往往会刻意避免大量的结构变更,并且尽可能将多次CSS操作合并起来。
回到我们的动画上,动画元素天然就是不停在动的,浏览器也就不得不反复的进行着计算工作。那我们该怎么尽可能减少计算压力呢?很简单,让动的元素超脱出来,不干扰其他元素的布局即可
用绝对定位可以不
我们脑子里一瞬间就闪出了万恶的绝对定位:
<style> .area { position: absolute; left: 10px; width: 50px; height: 50px; } #a { top: 10px; background-color: red; } #b { top: 65px; background-color: blue; } #c { top: 120px; background-color: green; animation: slidein 5s infinite; } @keyframes slidein { from { left: 10px; } to { left: 100px; } }</style>
绝对定位的元素确实脱离文档流了,但它真能让动画元素超脱出来,减轻浏览器负担么,我们可以用Chrome的Layers视图来看一下:
(控制台 => more => More tools => Layers)

右侧的立体图只看截图不太直观,但左侧的图层我们可以看到只有文档一层,元素并没有成功完成"飞升",这种情况下浏览器的计算工作依然巨大,所以单纯的绝对定位等脱离文档流的方式,并不能满足我们的需求呢。
如果能让动的元素成为单独的一层,其他不动的当做背景层,才算真正脱钩呢,那该怎么实现呢。
让GUP上吧
我们把CSS动画实现的代码再来改版一下:
@keyframes slidein { from { transform: translateX(0); } to { transform: translateX(100px); } }此时我们再来看一下Layers:

可以看到已经变成两层了呢,我们再换个角度截图:

可以看到绿色区域C已经完全脱离了原图层了呢,这是利用了CSS translate3d属性会使用GUP渲染的原理。
所以到目前为止我们终于可以得出一个有用的结论:动画元素尽可能用GUP渲染,最简单的是使用translate3d属性。
就这?
这种基础知识点确实没必要长篇大论说这么多,咱们接下来说说隐患。在我们的需求里,A和B元素都是不动的,只有最底层的C需要动起来,那么我们把需求改成让B动,C不用动了,图层的视角会怎么样咧?
<style> .area { position: absolute; left: 10px; width: 50px; height: 50px; } #a { top: 10px; background-color: red; } #b { top: 65px; background-color: blue; /* 只有B在动哦 */ animation: slidein 5s infinite; } #c { top: 120px; background-color: green; } @keyframes slidein { from { transform: translateX(0); } to { transform: translateX(0); } }</style>
这貌似不对呀?只有B用GUP提升了,为啥会有三个图层,而且为啥看立体图C还在B上面?
这里涉及到一个知识点,在都不用GUP提升的时候,所有元素都在一个图层上,文档的上下排列顺序无所谓,不会出现谁在谁上面的问题。但一旦其中一个元素被GUP单独渲染,那么在它文档结构下面的、且脱离了文档流的元素,则也会被单独渲染为一个图层,文档结构越靠下图层层级便越高。
这样会带来什么问题呢?每个图层都将占用相应的内存,图层越多内存占用越多,随着而来的就是卡顿甚至崩溃。
那如何避免生成不想要的图层呢?也很简单,给动画元素加上z-index就可以了。
最佳实践
所以我们针对动画元素应该:
- 让该元素脱离文档流,比如position:relative;
- 让该元素用GUP渲染到独立图层,比如:transform: translateZ(0);
- 给该元素设置z-index: 1;
我们来看看让B动起来的最终代码:
<style> .area { width: 50px; height: 50px; margin-bottom: 10px; } #a { background-color: red; } #b { position: relative; z-index: 1; background-color: blue; animation: slidein 5s infinite; } #c { background-color: green; } @keyframes slidein { from { transform: translateX(0); } to { transform: translateX(100px); } }</style>以及对应图层视图:
