写点什么

前端动效讲解与实战

  • 2022 年 9 月 26 日
    广东
  • 本文字数:14226 字

    阅读完需:约 47 分钟

作者:vivo 互联网前端团队- ZhaoJie


本文将从各个角度来对动画整个体系进行分类,并且介绍各种前端动画的实现方法,最后我们将总结在实际开发中的各个场景的动画选择方案。

一、背景

前端动画场景需求多


对众多动画场景的技术实现方案选择上比较模糊

各动画方案的优劣及适用场景认识模糊


现有动画库太多,不知道选哪个

主流动画库的适用场景认识模糊


下面首先让我们从各个角度来对动画整个体系进行分类,让我们清晰的了解动画整个体系。

二、分类

2.1  用途角度

首先我们从动画的用途或者说是业务的角度来进行区分,将我们平时的动画分为展示型动画和交互型动画。


2.1.1 展示型动画

类似于一张 GIF 图,或者一段视频。比如在开启宝箱的时候,我们会加入一个切场过渡动画,来替代原有的生硬等待结果。


展示型动画在实际使用的场景中,实现的方法很多,比如用 GIF 图,canvas,CSS3 动画等,但是最终输出的结果是不带有交互的,也就是从动画起始状态到结束状态一气呵成,这个过程用户可以感知,但是无法参与

2.1.2 交互型动画

用户自已参与的,对于交互性动画而言,我们可以在动画播放的某个时间节点触发相应的操作,进而让用户参与到其中,最常见的例子红包雨,不仅仅能提升用户的体验,还能提升我们的产品的多元性。


然而交互性动画经常面临的一个问题就是,通过原生代码实现交互动画是很复杂的,同时性能和兼容性是不得不认真考虑的问题,比较好的解决方案还是寻求相关的框架。

2.2 绘制技术角度

不管采用什么方式来制作动画,最终呈现到前端页面的无非是以下三种形式:

  1. Canvas

  2. div

  3. SVG

PS:为了简单也可以用视频,但除非动画的播放场景固定,不然移动端视频在不同 app、不同机型、不同系统的播放显示都不太一样,容易踩不少坑。


2.2.1 不同绘制技术的性能差异

Canvas

  • 效率高、性能好、可控性高,只能处理位图,内存占用恒定

  • 依赖分辨率

  • 不支持事件处理器

  • 弱的文本渲染能力

  • 能够以 .png 或 .jpg 格式保存结果图像

  • 最适合图像密集型的游戏,其中的许多对象会被频繁重绘


div

  • 包括 CSS 控制的 DOM 动画、JS 控制的 DOM 动画

  • 比较适合简单的数量较少的复杂度较低的动画


SVG

  • 处理矢量图,不失真

  • 不依赖分辨率

  • 支持事件处理器

  • 最适合带有大型渲染区域的应用程序(比如谷歌地图)

  • 复杂度高会减慢渲染速度(任何过度使用 DOM 的应用都不快)

  • 不适合游戏应用

2.2.2  Canvas 和 SVG 比较

一句话总结:都是 2D 做图,svg 是矢量图,canvas 是位图。canvas 是逐像素进行渲染的,适合游戏。


SVG

  • SVG 绘制的是矢量图,缩放不影响显示,所以最适合带有大型渲染区域的应用程序(比如谷歌地图)

  • SVG 是一种使用 XML 描述 2D 图形的语言。

  • SVG 基于 XML,这意味着 SVG DOM 中的每个元素都是可用的。您可以为某个元素附加 JavaScript 事件处理器。

  • 在 SVG 中,每个被绘制的图形均被视为对象。如果 SVG 对象的属性发生变化,那么浏览器能够自动重现图形。


Canvas

  • Canvas 通过 JavaScript 来绘制 2D 图形。

  • Canvas 是逐像素进行渲染的。

  • 在 Canvas 中,一旦图形被绘制完成,它就不会继续得到浏览器的关注。如果其位置发生变化,那么整个场景也需要重新绘制,包括任何或许已被图形覆盖的对象。

  • Canvas 只占用一个 DOM 节点,在做一些烟花、飘雪等运动元素很多的动画时,会比 CSS/SVG 性能好。


性能比较

  • 一般情况下,随着屏幕大小的增大,canvas 将开始降级,因为需要绘制更多的像素。

  • 随着屏幕上的对象数目增多,SVG 将开始降级,因为我们正不断将这些对象添加到 DOM 中。

  • 这些度量不一定准确,以下方面的不同一定会引起变化:实现和平台、是否使用完全硬件加速的图形,以及 JavaScript 引擎的速度。



2.3 动画类型角度

前端动效开发,首先应该确定的是

动画用途->确认动画类型->确认绘制技术->确认动画的实现方式。


虽然最终呈现动画的载体(绘制技术)就三种,但实现动画的方式却很多,得从动画类型出发讨论动画的实现方式:


(1)逐帧动画(序列帧动画)

  • GIF 实现

  • CSS 实现(animation)

  • JS+DOM 实现

  • JS+canvas 实现


(2)补间动画(Tween 动画\关键帧动画)

  • CSS 实现(transition、animation 等)使用一些缓动函数

  • JS 实现


(3)SVG 动画

  • 使用 XML 格式定义图形

  • 可以用 AI 等 SVG 编辑工具生成 SVG 图片后,配合 anime.js、GSAP 等现有库进行动画制作


(4)骨骼动画

  • 一般采用 Spine、DragonBones 等工具导出相应资源图片和 JSON 动画配置资源后使用。


(5)3D 动画

  • DOM 操作用 CSS 3D 实现。(perspective 属性、css3d-engine

  • 场景搭建用 webGL(Three.js 等)

  • 3D 模型动画用 Blender 或 maya 等制作完成后导出使用

2.3.1 逐帧动画(序列帧动画)


逐帧动画是在时间帧上逐帧绘制帧内容,由于是一帧一帧的画,所以逐帧动画具有非常大的灵活性,几乎可以表现任何想表现的内容。


由于逐帧动画的帧序列内容不一样,不仅增加制作负担而且最终输出的文件量也很大,但它的优势也很明显:因为它相似与电影播放模式,很适合于表演很细腻的动画,如 3D 效果、人物或动物急剧转身等等效果。


所以逐帧动画的实现核心是什么,就是将我们的这些静态的图片进行快速的循环播放,形成了一个动态的动画效果。这就是帧动画。

2.3.1.1 GIF 实现


我们可以将帧动画导出成 GIF 图,GIF 图会连续播放,无法暂停,它往往用来实现小细节动画,成本较低、使用方便。但其缺点也是很明显的:

  1. 画质上,GIF 支持颜色少(最大 256 色)、Alpha 透明度支持差,图像锯齿毛边比较严重;

  2. 交互上,不能直接控制播放、暂停、播放次数,灵活性差;

  3. 性能上,GIF 会引起页面周期性的绘画,性能较差。

2.3.1.2 CSS 实现


CSS3 帧动画是我们今天需要重点介绍的方案,最核心的是利用 CSS3 中 Animation 动画,确切的说是使用 animation-timing-function 的阶梯函数 steps(number_of_steps, direction) 来实现逐帧动画的连续播放。


帧动画的实现原理是不断切换视觉内图片内容,利用视觉滞留生理现象来实现连续播放的动画效果,下面我们来介绍制作 CSS3 帧动画的几种方案。


(1)连续切换动画图片地址 src(不推荐)

我们将图片放到元素的背景中(background-image),通过更改 background-image 的值实现帧的切换。但是这种方式会有以下几个缺点,所以该方案不推荐。

  • 多张图片会带来多个 HTTP 请求

  • 每张图片首次加载会造成图片切换时的闪烁

  • 不利于文件的管理


(2)连续切换雪碧图位置(推荐)我们将所有的帧动画图片合并成一张雪碧图,通过改变 background-position 的值来实现动画帧切换。分两步进行:


步骤一:

 将动画帧合并为雪碧图,雪碧图的要求可以看上面素材准备,比如下面这张帧动画雪碧图,共 20 帧。


(图片来源于:帧动画的多种实现方式与性能对比)


步骤二:

使用 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 动画在每一动画周期中执行的节奏。


综上我们可以知道,因为我们详细定义了一个动画周期,也就是说 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 步来阶段性展示。


(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);}}
复制代码


steps() 函数详解


从上面的代码我们可以发现,CSS 实现的核心就是使用 animation-timing-function 缓动函数的阶梯函数 steps(number_of_steps, direction)来实现逐帧动画的连续播放的。


接着我们来了解下 steps() 函数:

steps 指定了一个阶梯函数,包含两个参数:

  • 第一个参数指定了函数中的间隔数量(必须是正整数);

  • 第二个参数可选,指定在每个间隔的起点或是终点发生阶跃变化,接受 start 和 end 两个值,默认为 end。

  • start 第一帧是第一步动画的结束,end 第一帧是第一步动画的开始。



除了 steps 函数,animation-timing-function 还有两个与逐帧动画相关的属性值 step-start 与 step-end:

  • step-start 等同于 steps(1,start)

  • step-end 等同于 steps(1,end)


2.3.1.3 JS 实现


(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);})();
复制代码


2.3.1.4 性能分析


我们通过 Chrome 浏览器的各种工具,查看了每种方案的 FPS、CPU 占用率、GPU 占用、Scripting、Rendering、Painting、内存的使用情况,得到以下数据:



通过分析以上数据我们可以得出以下几点:

  1. 除了 CSS transform:translate3d() 方案,其他方案的 FPS 都能达到 60FPS 的流畅程度,但该方案的 FPS 也不是很低。

  2. CPU 占用率最低的方案是

    CSS transform:translate3d() 方案。

  3. GPU 占用最低的方案是 JS canvas 绘制方案。

  4. CSS 方案没有脚本开销。

  5. Rendering 最少的是

    CSS transform:translate3d() 方案。

  6. Painting 最少的是

    CSS transform:translate3d() 方案。

  7. 各方案内存占用区别不大。


结论:我们看到,在 7 个指标中,CSS transform:translate3d() 方案将其中的 4 个指标做到了最低,从这点看,我们完全有理由选择这种方案来实现 CSS 帧动画。


2.3.2 补间动画(Tween 动画\关键帧动画)


补间动画是动画的基础形式之一,又叫做中间帧动画,渐变动画,指的是人为设定动画的关键状态,也就是关键帧,而关键帧之间的过渡过程只需要由计算机处理渲染的一种动画形式。

说白了,就是我们在做动画的时候,只需要指定几个特殊时刻动画的状态,其余的状态由计算机自动计算补充。


实现补间动画常见的手段主要由以下几种:

  • CSS3 Animation:通过 animation(除 steps()以外的时间函数)属性在每个关键帧之间插入补间动画。

  • CSS3 Transition:区别于 animation,transition 只能设定初始和结束时刻的两个关键帧状态。

  • 利用 JavaScript 实现动画:例如 JavaScript 动画库或框架,Anime.js 或者 TweenJS,它是 CreateJS 的其中一个套件。另外,在 Flash 业界久负盛名的 GreenSock 推出的 GSAP(GreenSock Animation Platform)也新引入了对 Javascript 动画的支持。

2.3.2.1 CSS 实现


(1)transition 动画

transition 允许 CSS 的属性值在一定的时间区间内平滑地过渡,即指定元素的初始状态 和末尾状态,既可以完成一个动画,中间的变化完全有浏览器自己决定。动画的效果主要还是看 transition 相关属性即可。

然而利用 transition 制作的动画也有着显著的缺点:

  1. transition 需要事件触发,所以没法在网页加载时自动发生。

  2. transition 是一次性的,不能重复发生,除非一再触发。

  3. transition 只能定义开始状态和结束状态,不能定义中间状态,也就是说只有两个状态。

  4. 一条 transition 规则,只能定义一个属性的变化,不能涉及多个属性。


(2)animation 动画

利用 animation 可以完成一个完整的 CSS 补间动画,如上面所说,我们只需要定义几个特殊时刻的动画状态即可。这个特殊时刻通常我们叫做关键帧。


keyframes 关键帧

Keyframes 具有其自己的语法规则,他的命名是由"@keyframes"开头,后面紧接着是这个“动画的名称”加上一对花括号“{}”,括号中就是一些不同时间段样式规则,有点像我们 CSS 的样式写法一样。


对于一个"@keyframes"中的样式规则是由多个百分比构成的,如“0%”到"100%"之间,我们可以在这个规则中创建多个百分比,我们分别给每一个百分比中给需要有动画效果的元素加上不同的属性,从而让元素达到一种在不断变化的效果,比如说移动,改变元素颜色,位置,大小,形状等。


不过有一点需要注意的是,我们可以使用“fromt”“to”来代表一个动画是从哪开始,到哪结束,也就是说这个 "from"就相当于"0%"而"to"相当于"100%",值得一说的是,其中"0%"不能像别的属性取值一样把百分比符号省略,我们在这里必须加上百分符号(“%”)如果没有加上的话,我们这个 keyframes 是无效的,不起任何作用。因为 keyframes 的单位只接受百分比值。看一下具体的代码:

@keyframes IDENT {    from {        Properties:Properties value;    }    Percentage {        Properties:Properties value;    }    to {        Properties:Properties value;    }}/*或者全部写成百分比的形式:*/@keyframes IDENT {    0% {        Properties:Properties value;    }    Percentage {        Properties:Properties value;    }    100% {        Properties:Properties value;    }}
复制代码


其中 IDENT 是一个动画名称,你可以随便取,当然语义化一点更好,Percentage 是百分比值,我们可以添加许多个这样的百分比,Properties 为 CSS 的属性名,比如说 left,background 等,value 就是相对应的属性的属性值。

2.3.2.2 JS 实现


利用 JavaScript 实现动画,可以采用开源的 JavaScript 动画库或框架进行实现,例如:Anime.js 或者 TweenJS 下面我们以 Anime.js 为例进行演示如何实现一个补间动画。


一定程度上,anime.js 也是一个 CSS3 动画库,适用所有的 CSS 属性,并且实现的 @keyframes 能更方便的实现帧动画,替代 CSS3 复杂的定义方式。使用对象数组的形式定义每一帧。

戳我:keyframes实例

anime({     targets: 'div',     translateX: [         { value: 250, duration: 1000, delay: 500, elasticity: 0 }, //第一帧         { value: 0, duration: 1000, delay: 500, elasticity: 0 } //第二帧     ] }) //这个例子实现了目标元素在两帧中实现水平位移
复制代码


提供的 Timeline 能实现更为复杂的动画效果,通过这个 Timeline,我们可以维护不同的动画之间的关系,进而通过多个不同的动画组成一个更为复杂的动画。

戳我:Timeline实例

var myTimeline = anime.timeline(); //通过.add()方法添加动画 myTimeline .add({     targets: '.square',     translateX: 250 }) .add({     targets: '.circle',     translateX: 250 }) .add({     targets: '.triangle',     translateX: 250 });
复制代码

2.3.3 SVG 动画


当我们在实现动画的时候,慢慢会发现,大部分的元素都是图片,而且图片是提前预设好的,不能更改,只能用新的图片替换,例如当我们要实现微笑动画的时候,需要画两张图,一幅是闭着嘴的,一幅是张嘴笑的,然后逐帧播放。这样的画面当你有足够多帧图片的时候,并不会看出生硬,一旦低于 24 帧就是变得不自然了,那怎么在不增加工作量的前提下,实现流畅的变化呢?我们将关键帧动画的思维嫁接到元素自身扭曲变化上,就催生出了「柔性动画」的概念。


2.3.3.1 SVG 动画讲解


(图片来源于:GSAP官网)


从上图可以看出,元素之间是可以相互变化的,而且非常的流畅,这样的动画并不需要 canvas 这种重武器,简单的 DOM 就可以实现,SVG 真的是一个神器,不仅在实现图标,字体上特点鲜明,在实现柔性动画方面也独树一帜。


SVG 依然是 DOM ,他有自己独有的 Animation 标签,但也支持 CSS 的属性,其实现动画的本质是依赖于线条和填充,线条的变化,导致填充区域的改变,从而引起形状的变化。而线条则依赖于路径和锚点,路径和锚点的改变,直接影响了线条的变化。


可以用 AI 等 SVG 编辑工具生成 SVG 图片后,配合 anime.js、GSAP 等现有库进行动画制作。


下面我们通过 anime.js 来实现一个 SVG 路径动画.


SVG 绘制路径

戳我:SVG实例

var path = anime.path('.motion-path-demo path');

anime({ targets: '.motion-path-demo .el', translateX: path('x'), translateY: path('y'), rotate: path('angle'), easing: 'linear', duration: 2000, loop: true});
复制代码


(图片来源于:animejs官网)

2.3.4 骨骼动画


SVG 实现的动画比较局部和小巧,使用范围也比较狭窄,但是当我们实现复杂的柔性动画,甚至游戏的时候,就还是需要用骨骼动画来实现。


(图片来源于:DragonBones官网)


从上图我们可以看到龙的翅膀是一张图片,但是可以通过图片的局部的扭曲和变形,来实现煽动翅膀时带来的肌肉收缩和舒张。这样的动画是怎么实现的呢?这就要引出骨骼动画中,一个非常重要的概念:网格


这里我们比较浅显的讨论下这个概念,要实现图片的局部变化,我们就要把图片分块,分的每一块就称为网格,每个网格都有自己的顶点和边,顶点的位移会引起网格形状的变化,形状的变化就会带来所附属的图片的变化。网格的概念是不是很像路径和锚点,不论怎样的技术,在实现逻辑上都大同小异,重要的不是一直盯着不同和变化的部分,而是发现那些不变的地方,才能达到触类旁通的效果。


制作这样的动画并不复杂,你可以使用类似 Spine 和 DragonBones 这样的工具,但是做动画真的是一个体力活,你需要不断的调试,以求达到一种让人看起来舒服的状态。

2.3.4.1 骨骼动画讲解


骨骼动画就是把角色的各部分身体部件图片绑定到一根根互相作用连接的“骨头”上,通过控制这些骨骼的位置、旋转方向和放大缩小而生成的动画。


我们常说的骨骼动画一般分为两个部分:

  1. 骨架(Skeleton)

  2. 蒙皮(Skin)


骨架涉及的数据包括两个:

  • 一是骨架的拓扑结构(连接、父子关系)。

  • 二是骨架的各种 pose,也就是每个动作对应的整个骨架的位置信息。


蒙皮则表达的是依附在骨骼上的顶点的信息。

骨骼绑定的过程就是确定每个顶点受哪几根骨骼的影响,每根骨骼影响的权重有多大,譬如肘部的皮肤可能同时受大臂和小臂两根骨头的影响,而远离手肘的部分可能就只受小臂骨头影响。一般在 3D 骨骼动画里,每个顶点最多支持 4-8 根骨骼同时影响它就已经可以很精确地表达整个蒙皮的效果了。


  • 骨骼动画的优势:

骨骼动画比传统的逐帧动画要求更高的处理器性能,但同时它也具有更多的优势:

  1. 动画更加生动逼真。

  2. 图片资源占最小的存储空旷:骨骼动画的图片容量可以减少 90%(配置文件 H5 的压缩方案后面详解)。

  3. 动画切换自动补间:过渡动画自动生成,让动作更加灵动。

  4. 骨骼可控 :可以通过代码控制骨骼,轻松实现角色装备更换,甚至可对某骨骼做特殊控制或事件监听。

  5. 骨骼事件帧:动画执行到某个动作或某个帧,触发自定义事件行为。

  6. 动作数据继承:多角色可共用一套动画数据。

  7. 可结合物理引擎和碰撞检测。

2.3.4.2 骨骼动画制作


首先我们来了解一下,骨骼动画是如何进行制作的:

制作骨骼动画主要是使用 Spine 和 DragonBones 这样的工具进行制作。


  • DragonBones


(图片来源于:DragonBones官网)


DragonBones 是从 Flash 动画开始创作的,初衷是减小资源量,同时实现更为细粒度的动作(比如交互式的),让美术从繁琐的逐帧绘制 Sprie Sheet 的工作中解放出来,所以它把一个角色每一帧的 sprite sheet 拆分成一个个更小的基本图块,譬如胳膊,腿,躯干等等,而每个基本图块仍然是最小的可控制单位。

以下游戏 &渲染引擎都支持渲染 DragonBones 导出的文件:

(图片来源于:DragonBones官网)


  • Spine


(图片来源于:Spine官网)


Spine 是一款针对游戏开发的 2D 骨骼动画编辑工具。Spine 旨在提供更高效和简洁 的工作流程,以创建游戏所需的动画。

业界收费专业 2D 骨骼动画编辑工具,动画设计师推荐易用稳定,以下游戏 &渲染引擎都支持渲染 Spine 导出的文件:

(图片来源于:Spine官网)


下面我们来制作一个骨骼动画小案例


  • 创建骨骼

首先我们需要创建手部的骨骼,如下图所示:


  1. 1 确保左上角为 SETUP 模式

  2. 确保选中右边视图中的根骨骼,创建骨骼时必须要选中父骨骼

  3. 单击左下角的 Create 按钮

  4. 开始依次创建出 5 根骨骼


  • 创建蒙皮网格

然后我们需要给手部创建蒙皮网格(MESH),如下图所示: 


首先,单击创建骨骼的 Create 按钮,退出骨骼创建模式

  1. 选中手部贴图(Attachment)

  2. 勾选其底部的 Mesh 选项

  3. 单击右下角的 Edit 按钮

  4. 呼出了 Edit Mesh 菜单

  5. 勾选 Edit Mesh 菜单中的 Deformed 选项

  6. 单击 Edit Mesh 菜单中的 Create 按钮

  7. 开始在手部创建网格顶点

  8. 可以单击 Edit Mesh 菜单中的 Modify 按钮对顶点进行位移


  • 设置网格点权重

我们需要给网格顶点设置各个骨骼的权重,整个过程如下图所示:



首先,关闭 Edit Mesh 菜单

  1. 确认勾选的还是手部的贴图

  2. 单击左下角的 Weights 按钮,呼出 Weights 菜单

  3. 单击 Weights 菜单底部的 Bind 按钮,来绑定骨骼

  4. 选择手部的五根骨骼,直到它们都出现 Weights 菜单里,注意不同的骨骼颜色是不一样的

  5. 单击 Weights 菜单的 Auto 按钮或者按`esc`键,来触发 Spine 的自动权重计算

  6. 勾选 Weights 菜单的 Overlay,我们可以看到绑定后的权重热力图


  • 动起来!

现在我们要让手动起来了,我们只展示一个弯曲手臂的动画即可。

首先,我们需要设置关键帧,让我们在第 1 帧和第 30 帧设置好关键帧,这两个关键帧对应的手臂位置是完全一样的,因为我们需要循环播放动画。


具体步骤如下图:



  1. 确保左上角的模式处于 ANIMATE 模式

  2. 选中手部的五根骨骼(按住`cmd`键或`control`键依次点选)

  3.  选中第 0 帧

  4. 单击 Rotate 下的钥匙按钮,我们对手臂的旋转属性设置关键帧

  5. 选择第 30 帧

  6. 重复第 4 步的操作,使第 30 帧的关键帧与第 0 帧完全相同


接下来我们只需轻轻旋转手臂,并在 0-30 帧中间找一个帧当做关键帧即可:我们选择第 15 帧作为中间的关键帧。


  1. 选择第 15 帧

  2. 确保 Rotate 按钮被选中

  3. 向上旋转 5 根骨骼到一个角度

  4. 按下 K 帧按钮进行关键帧设置

  5. 按下播放按钮来预览动画


额外的,我给另一只手、嘴巴、脸部和头发都做了 MESH,以下是动画的效果图:



2.3.4.3 前端展示骨骼动画


用 Spine 将制作好的骨骼动画进行导出输出资源(合图信息文件:atlas;动画信息文件:json,图片合图:png),将这些资源交由前端进行展示。


前端开发根据 Spine 或者 DragonBones 能够支持的渲染引擎,在项目中导入渲染引擎进行展示骨骼动画。

2.3.5 3D 动画


前端 3D 动画实现可以通过 perspective 属性操作用 CSS 3D 来实现,或者直接借助开源的 Three.js 开源库进行实现。


由于 3D 动画涉及的内容较多,篇幅有限,后面我们将专门开一章来讲解前端 3D 动画。


三、现有方案总结

3.1 纯 CSS 实现

适合场景: 简单的展示型动画

使用 transition\animation 属性,设置相应的关键帧状态,并且借助一些缓动函数来进行实现一些简单化的动画。


优点:开发成本低,不需要导入任何额外的依赖包


缺点与不足:只能够胜任做一些比较简单化的动画,无法实现一些过于负责的动画。

3.2 Anime.js


适用场景: 简单的展示型动画+弱交互型动画


Anime.js 是一个轻量级的 js 驱动的动画库,主要的功能有:

  1. 支持 keyframes,连接多个动画

  2. 支持 Timeline,为实现更为复杂的动画提供了可能

  3. 支持动画状态的控制 playback control,播放,暂停,重新启动,搜索动画或时间线。

  4. 支持动画状态的 callback,在动画开始,执行中,结束时提供回调函数

  5. 支持 SVG 动画

  6. 可以自定义贝塞尔曲线

  7. 任何包含数值的 DOM 属性都可以设置动画



功能介绍:


一定程度上,anime.js 也是一个 CSS3 动画库,适用所有的 CSS 属性,并且实现的 @keyframes 能更方便的实现帧动画,替代 CSS3 复杂的定义方式。使用对象数组的形式定义每一帧。

戳我:keyframes实例

anime({     targets: 'div',     translateX: [         { value: 250, duration: 1000, delay: 500, elasticity: 0 }, //第一帧         { value: 0, duration: 1000, delay: 500, elasticity: 0 } //第二帧     ] }) //这个例子实现了目标元素在两帧中实现水平位移
复制代码


提供的 Timeline 能实现更为复杂的动画效果,通过这个 Timeline,我们可以维护不同的动画之间的关系,进而通过多个不同的动画组成一个更为复杂的动画。

戳我:Timeline实例

var myTimeline = anime.timeline(); //通过.add()方法添加动画 myTimeline .add({     targets: '.square',     translateX: 250 }) .add({     targets: '.circle',     translateX: 250 }) .add({     targets: '.triangle',     translateX: 250 });
复制代码


动画播放的控制,常见的有暂停,重播,继续,动画状态的跟踪,自动播放,循环次数,抖动效果

戳我:playback controls实例


为动画提供了回调函数,在动画或时间线完成的开始,期间或之时执行回调函数。

戳我:callback实例

var myAnimation = anime({     targets: '#begin .el',     translateX: 250,     delay: 1000,     begin: function(anim) { // callback         console.log(anim.began); // true after 1000ms     } });
复制代码


支持 promise,动画结束后,调用 anime.finished 会返回一个 promise 对象。

戳我:promise实例


支持 svg 绘制路径,目前不支持 canvas 绘制。

戳我:SVG实例


对于 input 这样带有数值的元素标签,也可以通过 anime 实例来设置动画。

戳我:DOM ATTRIBUTES实例

anime({     targets: input,     value: 1000, // Animate the input value to 1000     round: 1 // Remove decimals by rounding the value });
复制代码


优点:

  • 显而易见,anime.js 不仅实现了 CSS3 动画的深度封装,更多的是通过 js 驱动来实现操作动画的状态,timeline 实现了对于多个分支动画的管理,对于实现更为复杂的动画提供了可能。

  • 通过 anime.js 提供的 playback controls 和 callback,同时对于 promise 的支持,让我们对于动画的简单交互有了操作的空间。

  • 虽然不支持 canvas,但是支持 svg 绘制路径。

  • 浏览器兼容性比较好,Android 4 以上全部支持。

缺点:

Anime.js 做展示型动画是可以胜任的,但是对于特别复杂的动画也是不太能够实现,在做交互性动画方面还是需要看场景,它更多适合做一些小型的交互动画,类似于通过触摸屏幕踢足球这种强交互的,anime.js 就不是很有优势了。

3.3  Lottie

适用场景: 复杂的展示型动画

通过 AE 上的 Bodymovin 插件将 AE 中制作好的动画导出成一个 json 文件,通过 Lottie 对 JSON 进行解析,最后以 SVG/canvas/html 的方式渲染动画。


能够完好的展示设计师设计的各种各样复杂的动画。


优点:

  • 跨平台,一次绘制、一次转换、随处可用。

  • 文件更小,获取 AE 导出的 JSON,最后通过 lottie 渲染为 canvas/svg/html 格式。

  • 可以通过 api 操纵动画的一些属性,比如动画速度;添加动画各个状态的回调函数。

  • 动画都是在 After Effects 中创建的,使用 Bodymovin 导出,并且本机渲染无需额外的工程工作。

  • 解放前端工程师的生产力,提高设计师做动效的自由度。


缺点:

  • Bodymovin 插件待完善,仍然有部分 AE 效果无法成功导出。

  • 对于交互方面支持的还不是很好,更多的是用来展示动画。

  • Lottie 对 json 文件的支持待完善,目前有部分能成功导出成 json 文件的效果在移动端上无法很好的展现。

  • 很多 AE 的效果是不支持的 查看支持的特性:Supported Features

3.4 PixiJs

适用场景: 交互型动画,动画小游戏


PixiJS 是一个 2D 渲染引擎, Pixi 主要负责渲染画面。可以创建丰富的交互式图形,动画和游戏,而无需深入了解 WebGL API 或处理浏览器和设备兼容性的问题。与此同时,PixiJS 具有完整的 WebGL 支持,如果需要,可以无缝地回退到 HTML5 的 canvas。PixiJs 默认使用 WebGL 渲染,也可以通过声明指定 canvas 渲染,WebGL 在移动端 Android 4.4 browser 并不支持,不过可以使用 canvas 优雅降级。



特性(摘自官方 DOCS):

  • 支持 WebGL 渲染

  • 支持 canvas 渲染(官方称 PixiJS 在 canvas 渲染方面现在是最快的)

  • 非常简单易用的 API

  • 丰富的交互事件,比如完整的鼠标和移动端的触控事件

  • Pixi 使用和 canvas Drawing 几乎一致的 api,但不同于 canvas 的绘画 api,使用 Pixi 绘制的图形是通过 WebGL 在 GPU 上渲染

  • 还有一系列特性需要在学习 PixiJs 之后了解


优点:

  • 最大优势莫过于通过 WebGL 来调用 GPU 渲染动画,这样极大的提升了性能。

  • 无需深入了解 WebGL API 或者是浏览器兼容性(因为下面这条原因)。

  • 支持 canvas 回退,当前设备不支持 WebGL 时,PixiJs 会使用 canvas 渲染动画。

  • 完整的 DOCS,比较活跃的社区,有利于深入的学习。不过我感觉 PixiJs 学习成本相对来说还是很高的。


缺点:

  • 首先是兼容的问题,WebGL 在 Android 4.4 是不支持的,只能使用 canvas 进行降级。

  • Pixi 主要负责渲染画面,很多其它功能开发者得自己写或搭配其它库来使用,不过按照目前来看,是满足我们的需求的。


性能:

对于手机版本 Android4.4 以上的手机,除了代码层面造成的性能不足,通过 WebGL 调用 GPU 渲染,性能还是有保障的。然而对于 Android4.4 只能使用 canvas 渲染,性能还是要看动画的复杂度,以及代码的优化

3.5 总结


简单的展示型动画:

对于比较简单的动画,我们可以先尝试使用原生 CSS 的 transition\animation 属性来进行实现。


简单的展示型动画+弱交互:

对于简单的动画展示并且需要有简单的交互行为,比如用户点击一下暂停执行相应操作,待操作完成继续播放动画,交互方面比较偏弱,可以采用 Anime.js 的方案。


Anime.js 不仅仅支持所有的 CSS 属性,而且可以通过 Timeline,callback, playback controls 来控制动画执行的各个状态,并且 Anime.js 可以配合实现 SVG 动画。


复杂的展示型动画:

  1. 如果所需的资源很小,可以先考虑使用 GIF 动图或者逐帧动画 CSS 实现;

  2. 如果所需的资源较大,可以使用 Lottie 方案,然后设计同学用 AE 到处动画 json,将动画还原为 svg/canvas/html。


强交互 &互动小游戏 &骨骼动画:

  1. 对于交互场景比较负责或者需要做一个小游戏,可以采用 PixiJs,通过 WebGL 来渲染,利用硬件资源,极大的提升性能,在兼容性方面,对于不支持 WebGL 的浏览器,可以使用 canvas 渲染来平稳回退;

  2. 如果是需要展示骨骼动画,可以通过 PixiJs 方案进行渲染由 Spine 或 DragonBones 输出的文件。

发布于: 刚刚阅读数: 3
用户头像

官方公众号:vivo互联网技术,ID:vivoVMIC 2020.07.10 加入

分享 vivo 互联网技术干货与沙龙活动,推荐最新行业动态与热门会议。

评论

发布
暂无评论
前端动效讲解与实战_前端_vivo互联网技术_InfoQ写作社区