首先咱们来了解一下Web有哪些动画形式css
1\. CSS3动画 Transform(变形) Transition(过渡) Animation(动画) 2\. JS动画(操做DOM、修改CSS属性值) 3\. Canvas动画 4\. SVG动画 5\. 以Three.js为首的3D动画
以上各类动画形式均可以制做出一种类型的动画,那就是帧动画,也叫序列帧动画,定格动画,逐帧动画等,这里咱们统一用帧动画来表述。前端
帧动画通常用来实现稍微复杂一点的动画效果,同时但愿动画更细腻,设计师更自由的发挥。他能够定义到每个时间刻度上的展示内容,咱们通常用帧动画来作页面的Loading,小人物,小物体元素的简单动画。咱们想象中的帧动画应该有如下几个特色:canvas
帧动画的素材通常是先由设计师在PS中的时间轴上设计好了,而后导出图片给前端人员,PS制做时间轴动画通常是用来制做稍微简单的动画,操做简单,方便。浏览器
或者是由设计师在AE的时间轴进行设计,由于AE内置了更丰富的动做效果,好比转换,翻转之类的,AE能够帮助咱们实现更复杂的效果,而后再导出图片给前端人员。函数
这里帧动画素材的要求,每一帧的图片最好是偶数宽高,偶数张,最好周围能有一些留白。工具
将目前想到的解决方案梳理以下图,同时咱们将对每种方案进行详细介绍。性能
咱们能够将上面制做的帧动画导出成GIF图,GIF图会连续播放,没法暂停,它每每用来实现小细节动画,成本较低、使用方便。但其缺点也是很明显的:测试
CSS3帧动画是咱们今天须要重点介绍的方案,最核心的是利用CSS3中Animation动画,确切的说是使用animation-timing-function
的阶梯函数 steps(number_of_steps, direction)
来实现逐帧动画的连续播放。优化
帧动画的实现原理是不断切换视觉内图片内容,利用视觉滞留生理现象来实现连续播放的动画效果,下面咱们来介绍制做CSS3帧动画的几种方案。动画
(1)连续切换动画图片地址src(不推荐)
咱们将图片放到元素的背景中(background-image
),经过更改 background-image
的值实现帧的切换。可是这种方式会有如下几个缺点,因此该方案不推荐。
(2)连续切换雪碧图位置(推荐)
咱们将全部的帧动画图片合并成一张雪碧图,经过改变 background-position
的值来实现动画帧切换。分两步进行:
步骤一: 将动画帧合并为雪碧图,雪碧图的要求能够看上面素材准备,好比下面这张帧动画雪碧图,共20帧。
[图片上传失败...(image-91f7b7-1556627487319)]
<figcaption></figcaption>
步骤二: 使用steps阶梯函数切换雪碧图位置
先看写法一:
<div class="sprite"></div> .sprite { width: 300px; height: 300px; background-repeat: no-repeat; background-image: url(frame.png); animation: frame 333ms steps(1,end) both infinite; } @keyframes frame { 0% {background-position: 0 0;} 5% {background-position: -300px 0;} 10% {background-position: -600px 0;} 15% {background-position: -900px 0;} 20% {background-position: -1200px 0;} 25% {background-position: -1500px 0;} 30% {background-position: -1800px 0;} 35% {background-position: -2100px 0;} 40% {background-position: -2400px 0;} 45% {background-position: -2700px 0;} 50% {background-position: -3000px 0;} 55% {background-position: -3300px 0;} 60% {background-position: -3600px 0;} 65% {background-position: -3900px 0;} 70% {background-position: -4200px 0;} 75% {background-position: -4500px 0;} 80% {background-position: -4800px 0;} 85% {background-position: -5100px 0;} 90% {background-position: -5400px 0;} 95% {background-position: -5700px 0;} 100% {background-position: -6000px 0;} }
针对以上动画有疑问?
问题一:既然都详细定义关键帧了,是否是能够不用steps函数了,直接定义linear变化不就行了吗?
animation: frame 10s linear both infinite;
若是咱们定义成这样,动画是不会阶梯状,一步一步执行的,而是会连续的变化背景图位置,是移动的效果,而不是切换的效果,以下图:
问题二:不是应该设置为20步吗,怎么变成了1?
这里咱们先来了解下animation-timing-function
属性。
CSS animation-timing-function
属性定义CSS动画在每一动画周期中执行的节奏。对于关键帧动画来讲,timing function做用于一个关键帧周期而非整个动画周期,即从关键帧开始开始,到关键帧结束结束。
timing-function 做用于每两个关键帧之间,而不是整个动画。
接着咱们来了解下steps() 函数:
综上咱们能够知道,由于咱们详细定义了一个关键帧周期,从开始到结束,每两个关键帧之间分 1 步展现完,也就是说0% ~ 5%之间变化一次,5% ~ 10%变化一次,因此咱们这样写才能达到想要的效果。
再看写法二:
<div class="sprite"></div> .sprite { width: 300px; height: 300px; background-repeat: no-repeat; background-image: url(frame.png); animation: frame 333ms steps(20) both infinite; } @keyframes frame { 0% {background-position: 0 0;}//可省略 100% {background-position: -6000px 0;} }
这里咱们定义了关键帧的开始和结束,也就是定义了一个关键帧周期,但由于咱们没有详细的定义每一帧的展现,因此咱们要将0%~100%这个区间分红20步来阶段性展现。
也能够换成关键字的写法,还能够只定义最后一帧,由于默认第一帧就是初始位置。
@keyframes frame { from {background-position: 0 0;}//可省略 to {background-position: -6000px 0;} }
(3)连续移动雪碧图位置(移动端推荐)
跟第二种基本一致,只是切换雪碧图的位置过程换成了transform:translate3d()
来实现,不过要加多一层overflow: hidden;
的容器包裹,这里咱们以只定义初始和结束帧为例,使用transform能够开启GPU加速,提升机器渲染效果,还能有效解决移动端帧动画抖动的问题。
<div class="sprite-wp"> <div class="sprite"></div> </div> .sprite-wp { width: 300px; height: 300px; overflow: hidden; } .sprite { width: 6000px; height: 300px; will-change: transform; background: url(frame.png) no-repeat center; animation: frame 333ms steps(20) both infinite; } @keyframes frame { 0% {transform: translate3d(0,0,0);} 100% {transform: translate3d(-6000px,0,0);} }
(1)经过JS来控制img的src属性切换(不推荐)
和上面CSS3帧动画里面切换元素background-image
属性同样,会存在多个请求等问题,因此该方案咱们不推荐,可是这是一种解决思路。
(2)经过JS来控制Canvas图像绘制
经过Canvas制做帧动画的原理是用drawImage方法将图片绘制到Canvas上,不断擦除和重绘就能获得咱们想要的效果。
<canvas id="canvas" width="300" height="300"></canvas> (function () { var timer = null, canvas = document.getElementById("canvas"), context = canvas.getContext('2d'), img = new Image(), width = 300, height = 300, k = 20, i = 0; img.src = "frame.png"; function drawImg() { context.clearRect(0, 0, width, height); i++; if (i == k) { i = 0; } context.drawImage(img, i * width, 0, width, height, 0, 0, width, height); window.requestAnimationFrame(drawImg); } img.onload = function () { window.requestAnimationFrame(drawImg); } })();
上面是经过改变裁剪图像的X坐标位置来实现动画效果的,也能够经过改变画布上放置图像的坐标位置实现,以下: context.drawImage(img, 0, 0, width*k, height,-i*width,0,width*k,height);
。
(3)经过JS来控制CSS属性值变化
这种方式和前面CSS3帧动画同样,有三种方式,一种是经过JS切换元素背景图片地址background-image
,一种是经过JS切换元素背景图片定位background-position
,最后一种是经过JS移动元素transform:translate3d()
,第一种不作介绍,由于一样会存在多个请求等问题,不推荐使用,这里实现后面两种。
background-position
.sprite { width: 300px; height: 300px; background: url(frame.png) no-repeat 0 0; } <div class="sprite" id="sprite"></div> (function(){ var sprite = document.getElementById("sprite"), picWidth = 300, k = 20, i = 0, timer = null; // 重置背景图片位置 sprite.style = "background-position: 0 0"; // 改变背景图位置 function changePosition(){ sprite.style = "background-position: "+(-picWidth*i)+"px 0"; i++; if(i == k){ i = 0; } window.requestAnimationFrame(changePosition); } window.requestAnimationFrame(changePosition); })();
transform:translate3d()
.sprite-wp { width: 300px; height: 300px; overflow: hidden; } .sprite { width: 6000px; height: 300px; will-change: transform; background: url(frame.png) no-repeat center; } <div class="sprite-wp"> <div class="sprite" id="sprite"></div> </div> (function () { var sprite = document.getElementById("sprite"), picWidth = 300, k = 20, i = 0, timer = null; // 重置背景图片位置 sprite.style = "transform: translate3d(0,0,0)"; // 改变背景图移动 function changePosition() { sprite.style = "transform: translate3d(" + (-picWidth * i) + "px,0,0)"; i++; if (i == k) { i = 0; } window.requestAnimationFrame(changePosition); } window.requestAnimationFrame(changePosition); })();
总结以上几种方案,咱们能够看到GIF图有必定的优势同时缺点和局限性也比较明显,因此这种方案看状况选择使用。
其余实现方案的性能如何呢,咱们来比较一下,若是测试结果出现误差,可能与测试环境变化有关。
测试环境:
系统:Windows 10 专业版 处理器:Intel(R) Core(TM) i7-6700 CPU @ 3.40GHz 3.41GHz RAM: 8.00GB 浏览器:Chrome 72.0
CSS transform:translate3d()
方案性能数据
如上图,咱们经过Chrome浏览器的各类工具,查看了每种方案的 FPS、CPU占用率、GPU占用、Scripting、Rendering、Painting、内存的使用状况,获得如下数据:
性能-方案 | cssbackground-position |
csstransform:translate3d() |
JS Canvas | JSbackground-position |
JStransform:translate3d() |
---|---|---|---|---|---|
FPS | 60 | 51 | 60 | 60 | 60 |
CPU | 5%-6.2% | 0.3%-1% | 7%-8% | 6%-8% | 6%-8% |
GPU | 3.8MB | 4-10MB | 0 | 3.8MB | 4-11MB |
Scripting | 0 | 0 | 2.51% | 2.61% | 3.18% |
Rendering | 1.17% | 0.141% | 0.84% | 1.65% | 2.71% |
Painting | 1.58% | 0.01% | 1.63% | 1.75% | 1.05% |
内存 | 20112K | 21120K | 21588K | 20756K | 21576K |
经过分析以上数据咱们能够得出如下几点:
transform:translate3d()
方案,其余方案的FPS都能达到60FPS的流畅程度,但该方案的FPS也不是很低。transform:translate3d()
方案。transform:translate3d()
方案。transform:translate3d()
方案。结论:咱们看到,在7个指标中,css transform:translate3d()
方案将其中的4个指标作到了最低,从这点看,咱们彻底有理由选择这种方案来实现CSS帧动画。
至于其余方案的绝对比较暂时无法给出结论,看具体状况来选择,也看开发者对哪一个性能指标的追求。
延伸来看咱们的Web动画,每种形式的动画都有其各自的有点,好比大量的粒子效果用Canvas绘制方案确定要比DOM+CSS实现要好的,大量的CSS属性值变换,使用 transform
实现性能是要更好的。
素材:动画图片宽高最好是偶数,总帧数最好是偶数,图片拼接处最好有必定的留白。
适配:移动端适配最好不用rem,由于rem的计算会形成小数四舍五入,形成必定的抖动效果,建议直接用px做为单位,同时辅助以scale(zoom)媒体查询进行适配。若是使用rem适配,试试使用transform的方案,抖动问题能够获得优化解决。
tips:使用 will-change
能够在元素属性真正发生变化以前提早作好对应准备。
本文咱们主要梳理了目前实现帧动画的几种方案,同时对各类方案进行效果实现,优劣讨论,性能对比,同时简单介绍了帧动画实现过程的注意事项,最后咱们得出结论,css transform:translate3d()
方案在实现和性能上都明显优于其余方案。