查看原文
其他

【第2159期】使用Lottie 快速开发动画

京东用户体验设计 前端早读课 2021-11-09

前言

不知道大家现在开发精细化动画都采用什么方案?今日前端早读课文章由@京东用户体验设计部投稿分享。

@京东用户体验设计部-前端开发部现有前端开发人员 50 左右,主要为京东零售集团、京东健康提供 WEB 前端开发、APP RN开发,小程序开发、小游戏开发、H5开发等能力支持。

正文从这开始~~

背景

作为一个前端,所处理的任何功能都要直面客户,为了提高用户体验,经常会遇到一些微小动画交互。如下图:



这些微小的动画看似简单,但前端应该使用什么方法来实现呢?

前端常见动画处理方式

如果只是一个背景展示,设计输出如下的雪碧图,然后前端合理的使用 Animation可以实现很多神奇效果。在这里再次感叹一下 Css3 带来的神奇之处。


但是这种效果会导致放大失帧,图片加载过慢,甚至不方便一些稍微复杂的动画处理。

Gif 图简单粗暴,但是 Gif 图是位图,不方便控制动画,放大会虚。

Js + Canvas/svg。无论是 Js 操作 Dom 还是用 Svg API 来实现。都算是比较良好的方案,但是成本较高。

所以,这里我们重磅推荐 Lottie 。

Lottie 介绍

Lottie 是一个跨平台的动画库。设计小姐姐使用 AE 制作并导出的动画文件( json 格式),Lottie 可以在各个终端快速解析这个 json 文件,从而还原动画,简单快速。

那么,接下来,我们将使用 Lottie 来完成一个动画的制作。

开始开发

第一步 构建 json 文件

Lottie 可以友好的应用于前端的各种框架(原生,VUE,React , Angular),下面是我们今天要完成的一个动画。首先看一下效果图。

这是一个开奖的动画。我们希望用户在操作之后展示这样的交互。今天我们使用 vue-lottie 来完成我们的表演。

首先我们把设计好的动画转换成 json 文件。转换的过程大概就是在 AE 下制作完成动画,然后安装 bodymovin 插件(管理员权限)。接下来在 AE 中使用点击 窗口--->扩展--> bodymovin 出现弹窗,点击 RENDER 即可生成我们需要的 json 文件。

第二步 引入所需要的资源

我们在构建好的 VUE 项目中导入上一步做好的 json 文件。具体的目录我推荐存放在 src/asset/lottie,如图

这是一个构建好的 json 文件 。

  1. {

  2. "v": "5.5.7", // bodymovin 版本

  3. "meta":{} //一些动画版本信息

  4. "fr": 12, // 帧率

  5. "ip": 0, // 起始关键帧

  6. "op": 88, // 结束关键帧

  7. "w": 4000, // 视图宽

  8. "h": 4000, // 视图高

  9. "nm": "Comp 1", // 名称

  10. "ddd": 0, // 3d

  11. "assets": [], // 资源集合

  12. "layers": [], // 图层集合

  13. "masker": [] // 蒙层集合

  14. }

上面给出了 json 文件中各个参数的含义。其中 ip 表示关键帧,一般为0,op 表示动画的结束关键帧,fr表示帧率,所以动画时间等于: (op-ip)/fr 。w和h分别表示视图的宽和高。

由于 assets、layers、masker 里面的数据可能很大,所以上面用空数组代替。其中 layers 是一个图层集合,它里面数据一般很大,里面包含了当前动画的所有图层数据,assets 是一个资源集合,它里面包含了当前动画使用的资源图层数据。masks 则表示蒙层集合,里面包含了所有的蒙层数据。 Lottie 其实就是操作图层 layer 的偏移缩放来实现动画的,具体原理我们以后再详解。

接下来我们就可以再项目中引入 vue-lottie 了。简单的操作 npm install --save vue-lottie 即可完成。

第三步 实现动画

接下来我们就在项目中使用 vue-lottie 。即 可以将其设置为全局组件,也可以在需要使用的时候单独引入。

第一种:全局引入,在 main.js 中加入如下代码

  1. import lottie from 'vue-lottie'

  2. Vue.component('lottie',lottie)

第二种:局部引入

在 vue 文件中引用 vue-lottie ,然后在组件中进行注册

  1. import lottie from 'vue-lottie'


  2. export default{

  3. ...

  4. components:{

  5. 'lottie':lottie

  6. }

  7. ...

  8. }

接下来,在实现动画的页面引入我们第二步设置好的 json 文件。

  1. import award from "../assets/lottie/award.json"

然后在页面上使用动画

  1. <lottie :options="defaultOptions" :width="24" :height="24" v-on:animCreated="handleAnimation" />


  2. data:{

  3. return{

  4. defaultOptions:{animationData:award,loop:false,autoplay:false},

  5. defaultLottie:''

  6. }

  7. },

  8. methods:{

  9. handleAnimation(anim){

  10. this.defaultLottie = anim;

  11. }

  12. }

说明:Lottie 初始化时,需要指定一个 options 配置项,因为 Lottie 是没有宽高的,所以宽高必须设置,animCreated 方法用于创建 Lottie 动画,它的方法返回创建动画的对象实例,里面包含这个动画的所有属性。我们可以使用这个对象,来完成对动画的所有操作,包括暂停,播放等。

Lottie 的参数 options 说明:

  1. container: element, //要包含该动画的dom元素

  2. renderer: 'svg', //渲染方式,svg、canvas、html(轻量版仅svg渲染)

  3. loop: true, //是否循环播放,true表示循环,false表示不循环

  4. autoplay: true, //是否自动播放,true表示自动播放

  5. animationData: animationData, //需要引入的json动画对象

综上所述,一个完整的动画就跃然纸上了。但是我们是不是还能对动画进行控制操作呢?

第四步 操作进阶

假如我们希望某个按钮操作之后,动画才开始播放。这如何实现呢?亦或者我们希望动画结束后,自动进行别的操作呢?

接上面的代码,我们只需要在点击事件里面,直接对动画进行 play 操作即可完成对动画的操作。

  1. methods: {

  2. //点击事件

  3. fn() {

  4. this.defaultLottie.play(); // 播放该动画,从目前停止的帧开始播放

  5. }

  6. }

其他操作的 API 如下

  1. this.defaultLottie.play(); // 播放该动画,从目前停止的帧开始播放

  2. this.defaultLottie.stop(); // 停止播放该动画,回到第0帧

  3. this.defaultLottie.pause(); // 暂停该动画,在当前帧停止并保持


  4. this.defaultLottie.goToAndStop(value, isFrame); // 跳到某个时刻/帧并停止。isFrame(默认false)指示value表示帧还是时间(毫秒)

  5. this.defaultLottie.goToAndPlay(value, isFrame); // 跳到某个时刻/帧并从该帧往后进行播放

  6. this.defaultLottie.goToAndStop(30, true); // 跳转到第30帧并停止

  7. this.defaultLottie.goToAndPlay(300); // 跳转到第300毫秒并从300毫秒开始向后播放


  8. this.defaultLottie.playSegments(arr, forceFlag); // 播放片段,arr可以包含两个数字或者两个数字组成的数组,forceFlag表示是否立即强制播放该片段,如果是false则需等前一个动画结束后开始播放,如果是true,则立即开始播放

  9. this.defaultLottie.playSegments([10,20], false); // 播放完之前的片段,播放10-20帧

  10. this.defaultLottie.playSegments([[0,5],[10,18]], true); // 直接播放0-5帧和10-18帧


  11. this.defaultLottie.setSpeed(speed); // 设置播放速度,speed为1表示正常速度

  12. this.defaultLottie.setDirection(direction); // 设置播放方向,1表示正向播放,-1表示反向播放

  13. this.defaultLottie.destroy(); // 删除该动画,移除相应的元素标签等。在unmount的时候,需要调用该方法

同时,Lottie 也提供了对动画的监听,我们可以在 mounted 中对动画结束进行监听。

  1. this.defaultLottie.addEventListener('loopComplete', () => {

  2. console.log('动画已经结束');

  3. });

Lottie 提供了丰富的监听事件,我们可以欣赏一下。

  1. complete: 播放完成后触发(循环播放下不会触发)

  2. loopComplete: 当前循环下播放(循环播放/非循环播放)结束时触发

  3. enterFrame: 每进入一帧就会触发,播放时每一帧都会触发一次,stop方法也会触发

  4. segmentStart: 播放指定片段开始时触发,playSegments、resetSegments等方法刚开始播放指定片段时会发出,如果playSegments播放多个片段,多个片段最开始都会触发。

  5. data_ready: 动画json文件加载完毕触发

  6. DOMLoaded: 动画相关的dom已经被添加到html后触发

  7. destroy: 将在动画删除时触发

第五步 原理探究

vue-lottie 是如何实现将json文件转换成动画显示到浏览器上的呢?我们打开 vue-lottie 的源码。

  1. <template>

  2. <div :style="style" ref="lavContainer"></div>

  3. </template>


  4. <script>

  5. import lottie from 'lottie-web';

  6. export default { // ...},

  7. data () {

  8. return {

  9. //...

  10. }

  11. },

  12. mounted () {

  13. this.anim = lottie.loadAnimation({

  14. container: this.$refs.lavContainer,

  15. renderer: 'svg',

  16. loop: this.options.loop !== false,

  17. autoplay: this.options.autoplay !== false,

  18. animationData: this.options.animationData,

  19. rendererSettings: this.options.rendererSettings

  20. }

  21. );

  22. this.$emit('animCreated', this.anim)

  23. }

  24. }

  25. </script>

其实,所有 web 端能够实现 Lottie 的功能,都是引用了 lottie-web 插件。在 lottie-web 中对外提供一个loadAnimation 的方法,来加载和解析 json ,播放动画。

  1. function loadAnimation(params){

  2. var animItem = new AnimationItem(); // 新建对象

  3. setupAnimation(animItem, null); // 主要是添加一些时间监听函数

  4. animItem.setParams(params); // 根据输入的参数和json数据,渲染成相应的动画

  5. return animItem;

  6. }

继续深究,我们看到,整个动画的实现其实就是 AnimtionItem 类的一个实例。一个 AnimationItem 实例,包含了很多的属性,比如:playSpeed、playDirection、loop 等等(不仅仅是下面代码中的属性,还有大量的属性在下面的代码中没有罗列出来)。

  1. var AnimationItem = function () {

  2. this.playSpeed = 1;

  3. this.isPaused = true;

  4. this.autoplay = false;

  5. this.loop = true;

  6. }


  7. AnimationItem.prototype.setParams = function(params) {

  8. var animType = params.animType ? params.animType : params.renderer ? params.renderer : 'svg';

  9. switch(animType){

  10. case 'canvas':

  11. this.renderer = new CanvasRenderer(this, params.rendererSettings);break;

  12. case 'svg':

  13. this.renderer = new SVGRenderer(this, params.rendererSettings); break;

  14. //....

  15. }

  16. }

由上面结果看出来,SVGRenderer 、CanvasRenderer 才是 Lottie 的重要组成部分,Lottie 是通过这两个函数来实现对 json 的解析。我们以 SVGRenderer 为例,尝试为大家展示出如何根据 json 参数来渲染 SVG 元素的步骤和方法。

我们在第二步的时候,分析过 json 数据,其中有一个图层元素 layer 。其实所有的动画都是由一个个图层构成。Lottie 在图层上进行偏移、缩放等操作来实现动画的。图层的解析是 Lottie 的主要功能模块。 一个layer图层的数据格式一般如下:

  1. {

  2. "ddd": 0, // 是否为3d

  3. "ind": 1, // layer的ID,唯一

  4. "ty": 0, // 图层类型。

  5. "nm": "鹅头收起", // 图层名称

  6. "refId": "comp_0", // 引用的资源,图片/预合成层

  7. "sr": 1,

  8. "ks": {}, // 变换。对应AE中的变换设置,下面有详细介绍

  9. layer: [], // 该图层包含的子图层

  10. shaps: [], // 形状图层

  11. "ao": 0,

  12. "w": 1334,

  13. "h": 750,

  14. "ip": 0, // 该图层开始关键帧

  15. "op": 60, // 该图层结束关键帧

  16. "st": 0, // 该图层

  17. "bm": 0

  18. }

其中说明一下 nm 属性,该属性是在 AE 中对该图层的命名,通过在 SVG 中修改该命名,可以设置对应的 SVG 的 class 和 id。如果命名为 '#svgId',生成的对应的 SVG 元素的 id 则为 'svgId' ;如果命名为 '.svg-class',则生成的对应的 SVG 元素的 class 为 'svg-class' 。

ty 表示类型,例如:

  • 2: image,图片

  • 0: comp,合成图层

  • 1: solid;

  • 3: null;

  • 4: shape,形状图层

  • 5: text,文字

然后 Lottie 对 layers 图层数据相应的处理有:(下面为核心代码,详情参考源码 createItem 和 SVGCompElement 方法)

  1. SVGRenderer.prototype.buildItem = function(pos){

  2. var elements = this.elements;

  3. var element = this.createItem(this.layers[pos]); // 创建元素


  4. elements[pos] = element; // 将元素添加到svg中

  5. this.appendElementInPos(element,pos);

  6. };

  7. BaseRenderer.prototype.createItem = function(layer){

  8. switch(layer.ty){ // 根据图层类型,创建相应的svg元素类的实例

  9. case 2:

  10. return this.createImage(layer);

  11. case 0:

  12. return this.createComp(layer);

  13. case 1 ....

  14. }

  15. return this.createNull(layer);

  16. };

经过上述的操作,我们所需要的 DOM 元素其实以及完成了,但是所有 DOM 是如何按照我们的要求进行动起来呢?这里我们揭秘一下 Lottie 是如何让 DOM 动起来的。

  1. "ks": { // 变换。对应AE中的变换设置

  2. "o": { // 透明度

  3. "a": 0,

  4. "k": 100,

  5. "ix": 11

  6. },

  7. "r": { // 旋转

  8. "a": 0,

  9. "k": 0,

  10. "ix": 10

  11. },

  12. "p": { // 位置

  13. "a": 0,

  14. "k": [-167, 358.125, 0],

  15. "ix": 2

  16. },

  17. "a": { // 锚点

  18. "a": 0,

  19. "k": [667, 375, 0],

  20. "ix": 1

  21. },

  22. "s": { // 缩放

  23. "a": 0,

  24. "k": [100, 100, 100],

  25. "ix": 6

  26. }

  27. }

上面这个对象是 layer 中的 ks 属性,ks 对应 AE 中 图层的变换属性,可以通过设置锚点、位置、旋转、缩放、透明度等来控制图层,并设置这些属性的变换曲线,来实现动画。lottie-web 会把 ks 处理成 transform 的属性,用于对元素进行变换操作。transform 包含了 translate(平移)、scale(缩放)、rotate(旋转)、skew(倾斜)等几种。lottie-web 中处理 ks(变换)的相关代码为:

  1. function getTransformProperty(elem,data,container){ // data为ks数据

  2. return new TransformProperty(elem,data,container);

  3. }

  4. function TransformProperty(elem,data,container){

  5. // ...

  6. if(data.sk){ // 获取skew相关的参数

  7. this.sk = PropertyFactory.getProp(elem, data.sk, 0, degToRads, this);

  8. this.sa = PropertyFactory.getProp(elem, data.sa, 0, degToRads, this);

  9. }

  10. if(data.a) { // 获取translate相关的参数

  11. this.a = PropertyFactory.getProp(elem,data.a,1,0,this);

  12. }

  13. if(data.s) { // 获取scale相关的参数

  14. this.s = PropertyFactory.getProp(elem,data.s,1,0.01,this);

  15. }


  16. }

  17. function applyToMatrix(mat) { //使用transfrom 属性实现动画

  18. ...

  19. if (this.a) {

  20. mat.translate(-this.a.v[0], -this.a.v[1], this.a.v[2]);

  21. }

  22. if (this.s) {

  23. mat.scale(this.s.v[0], this.s.v[1], this.s.v[2]);

  24. }

  25. if (this.sk) {

  26. mat.skewFromAxis(-this.sk.v, this.sa.v);

  27. }

  28. if (this.r) {

  29. mat.rotate(-this.r.v);

  30. } else {

  31. mat.rotateZ(-this.rz.v).rotateY(this.ry.v).rotateX(this.rx.v).rotateZ(-this.or.v[2]).rotateY(this.or.v[1]).rotateX(this.or.v[0]);

  32. }

  33. if (this.data.p.s) {

  34. if (this.data.p.z) {

  35. mat.translate(this.px.v, this.py.v, -this.pz.v);

  36. } else {

  37. mat.translate(this.px.v, this.py.v, 0);

  38. }

  39. } else {

  40. mat.translate(this.p.v[0], this.p.v[1], -this.p.v[2]);

  41. }

  42. }

以上就是 Lottie 来实现一个动画的简单过程,其中还涉及到 shape 的图形学的使用,我们这里就不再深究。

第六步 注意事项

在使用 AE 转 json 的时候,如果动画中有图片,请注意转出来的图片在 json 中的路径保持一致。或者需要修改 json 中的图片路径,建议图片路径修改为线上,同时可以避免 json 文件过大。

Lottie 对于一些交互性的动画,支持不是很好。主要是对于播放性的动画。

当 Lottie 使用 svg 模式进行播放动画的时候,可以完整动画播放,但如果是首屏加载或者动画过大,进入页面的一瞬间会卡的跳帧,原因是大量 dom 节点导致首屏性能不友好。

因为 Lottie 在 web 上原理是使用了 transfrom 属性,所有在 设计动画的时候,尽量只使用 AE 的五大属性(位置,缩放,旋转,透明度,锚点)以及修剪路径和路径动画。其他的如滤镜、表达式、混合模式、亮度遮罩、图层效果等慎用。

结束语

至此。我们关于 Lottie 的使用的到此结束。谢谢大家的观看,希望能对大家以后的开发起到一些帮助。

推荐参考资料:

Lottie—json文件解析:https://blog.csdn.net/x85371169/article/details/100740034

Lottie的动画Demo:https://chenqingspring.github.io/vue-lottie/

Lottie源码分析:http://test.imweb.io/topic/5b2b129e61340cbe5576ca44

初次使用 Lottie 的一点防坑指南:https://jelly.jd.com/article/5f742405aef0130151aee9cf

为你推荐


【第921期】大杀器Bodymovin和Lottie:把AE动画转换成HTML5/Android/iOS原生动画


【第1542期】让动画变得更简单之FLIP技术


欢迎自荐投稿,前端早读课等你来


: . Video Mini Program Like ,轻点两下取消赞 Wow ,轻点两下取消在看

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存