【第2159期】使用Lottie 快速开发动画
前言
不知道大家现在开发精细化动画都采用什么方案?今日前端早读课文章由@京东用户体验设计部投稿分享。
@京东用户体验设计部-前端开发部现有前端开发人员 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 文件 。
{
"v": "5.5.7", // bodymovin 版本
"meta":{} //一些动画版本信息
"fr": 12, // 帧率
"ip": 0, // 起始关键帧
"op": 88, // 结束关键帧
"w": 4000, // 视图宽
"h": 4000, // 视图高
"nm": "Comp 1", // 名称
"ddd": 0, // 3d
"assets": [], // 资源集合
"layers": [], // 图层集合
"masker": [] // 蒙层集合
}
上面给出了 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 中加入如下代码
import lottie from 'vue-lottie'
Vue.component('lottie',lottie)
第二种:局部引入
在 vue 文件中引用 vue-lottie ,然后在组件中进行注册
import lottie from 'vue-lottie'
export default{
...
components:{
'lottie':lottie
}
...
}
接下来,在实现动画的页面引入我们第二步设置好的 json 文件。
import award from "../assets/lottie/award.json"
然后在页面上使用动画
<lottie :options="defaultOptions" :width="24" :height="24" v-on:animCreated="handleAnimation" />
data:{
return{
defaultOptions:{animationData:award,loop:false,autoplay:false},
defaultLottie:''
}
},
methods:{
handleAnimation(anim){
this.defaultLottie = anim;
}
}
说明:Lottie 初始化时,需要指定一个 options 配置项,因为 Lottie 是没有宽高的,所以宽高必须设置,animCreated 方法用于创建 Lottie 动画,它的方法返回创建动画的对象实例,里面包含这个动画的所有属性。我们可以使用这个对象,来完成对动画的所有操作,包括暂停,播放等。
Lottie 的参数 options 说明:
container: element, //要包含该动画的dom元素
renderer: 'svg', //渲染方式,svg、canvas、html(轻量版仅svg渲染)
loop: true, //是否循环播放,true表示循环,false表示不循环
autoplay: true, //是否自动播放,true表示自动播放
animationData: animationData, //需要引入的json动画对象
综上所述,一个完整的动画就跃然纸上了。但是我们是不是还能对动画进行控制操作呢?
第四步 操作进阶
假如我们希望某个按钮操作之后,动画才开始播放。这如何实现呢?亦或者我们希望动画结束后,自动进行别的操作呢?
接上面的代码,我们只需要在点击事件里面,直接对动画进行 play 操作即可完成对动画的操作。
methods: {
//点击事件
fn() {
this.defaultLottie.play(); // 播放该动画,从目前停止的帧开始播放
}
}
其他操作的 API 如下
this.defaultLottie.play(); // 播放该动画,从目前停止的帧开始播放
this.defaultLottie.stop(); // 停止播放该动画,回到第0帧
this.defaultLottie.pause(); // 暂停该动画,在当前帧停止并保持
this.defaultLottie.goToAndStop(value, isFrame); // 跳到某个时刻/帧并停止。isFrame(默认false)指示value表示帧还是时间(毫秒)
this.defaultLottie.goToAndPlay(value, isFrame); // 跳到某个时刻/帧并从该帧往后进行播放
this.defaultLottie.goToAndStop(30, true); // 跳转到第30帧并停止
this.defaultLottie.goToAndPlay(300); // 跳转到第300毫秒并从300毫秒开始向后播放
this.defaultLottie.playSegments(arr, forceFlag); // 播放片段,arr可以包含两个数字或者两个数字组成的数组,forceFlag表示是否立即强制播放该片段,如果是false则需等前一个动画结束后开始播放,如果是true,则立即开始播放
this.defaultLottie.playSegments([10,20], false); // 播放完之前的片段,播放10-20帧
this.defaultLottie.playSegments([[0,5],[10,18]], true); // 直接播放0-5帧和10-18帧
this.defaultLottie.setSpeed(speed); // 设置播放速度,speed为1表示正常速度
this.defaultLottie.setDirection(direction); // 设置播放方向,1表示正向播放,-1表示反向播放
this.defaultLottie.destroy(); // 删除该动画,移除相应的元素标签等。在unmount的时候,需要调用该方法
同时,Lottie 也提供了对动画的监听,我们可以在 mounted 中对动画结束进行监听。
this.defaultLottie.addEventListener('loopComplete', () => {
console.log('动画已经结束');
});
Lottie 提供了丰富的监听事件,我们可以欣赏一下。
complete: 播放完成后触发(循环播放下不会触发)
loopComplete: 当前循环下播放(循环播放/非循环播放)结束时触发
enterFrame: 每进入一帧就会触发,播放时每一帧都会触发一次,stop方法也会触发
segmentStart: 播放指定片段开始时触发,playSegments、resetSegments等方法刚开始播放指定片段时会发出,如果playSegments播放多个片段,多个片段最开始都会触发。
data_ready: 动画json文件加载完毕触发
DOMLoaded: 动画相关的dom已经被添加到html后触发
destroy: 将在动画删除时触发
第五步 原理探究
vue-lottie 是如何实现将json文件转换成动画显示到浏览器上的呢?我们打开 vue-lottie 的源码。
<template>
<div :style="style" ref="lavContainer"></div>
</template>
<script>
import lottie from 'lottie-web';
export default { // ...},
data () {
return {
//...
}
},
mounted () {
this.anim = lottie.loadAnimation({
container: this.$refs.lavContainer,
renderer: 'svg',
loop: this.options.loop !== false,
autoplay: this.options.autoplay !== false,
animationData: this.options.animationData,
rendererSettings: this.options.rendererSettings
}
);
this.$emit('animCreated', this.anim)
}
}
</script>
其实,所有 web 端能够实现 Lottie 的功能,都是引用了 lottie-web 插件。在 lottie-web 中对外提供一个loadAnimation 的方法,来加载和解析 json ,播放动画。
function loadAnimation(params){
var animItem = new AnimationItem(); // 新建对象
setupAnimation(animItem, null); // 主要是添加一些时间监听函数
animItem.setParams(params); // 根据输入的参数和json数据,渲染成相应的动画
return animItem;
}
继续深究,我们看到,整个动画的实现其实就是 AnimtionItem 类的一个实例。一个 AnimationItem 实例,包含了很多的属性,比如:playSpeed、playDirection、loop 等等(不仅仅是下面代码中的属性,还有大量的属性在下面的代码中没有罗列出来)。
var AnimationItem = function () {
this.playSpeed = 1;
this.isPaused = true;
this.autoplay = false;
this.loop = true;
}
AnimationItem.prototype.setParams = function(params) {
var animType = params.animType ? params.animType : params.renderer ? params.renderer : 'svg';
switch(animType){
case 'canvas':
this.renderer = new CanvasRenderer(this, params.rendererSettings);break;
case 'svg':
this.renderer = new SVGRenderer(this, params.rendererSettings); break;
//....
}
}
由上面结果看出来,SVGRenderer 、CanvasRenderer 才是 Lottie 的重要组成部分,Lottie 是通过这两个函数来实现对 json 的解析。我们以 SVGRenderer 为例,尝试为大家展示出如何根据 json 参数来渲染 SVG 元素的步骤和方法。
我们在第二步的时候,分析过 json 数据,其中有一个图层元素 layer 。其实所有的动画都是由一个个图层构成。Lottie 在图层上进行偏移、缩放等操作来实现动画的。图层的解析是 Lottie 的主要功能模块。 一个layer图层的数据格式一般如下:
{
"ddd": 0, // 是否为3d
"ind": 1, // layer的ID,唯一
"ty": 0, // 图层类型。
"nm": "鹅头收起", // 图层名称
"refId": "comp_0", // 引用的资源,图片/预合成层
"sr": 1,
"ks": {}, // 变换。对应AE中的变换设置,下面有详细介绍
layer: [], // 该图层包含的子图层
shaps: [], // 形状图层
"ao": 0,
"w": 1334,
"h": 750,
"ip": 0, // 该图层开始关键帧
"op": 60, // 该图层结束关键帧
"st": 0, // 该图层
"bm": 0
}
其中说明一下 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 方法)
SVGRenderer.prototype.buildItem = function(pos){
var elements = this.elements;
var element = this.createItem(this.layers[pos]); // 创建元素
elements[pos] = element; // 将元素添加到svg中
this.appendElementInPos(element,pos);
};
BaseRenderer.prototype.createItem = function(layer){
switch(layer.ty){ // 根据图层类型,创建相应的svg元素类的实例
case 2:
return this.createImage(layer);
case 0:
return this.createComp(layer);
case 1 ....
}
return this.createNull(layer);
};
经过上述的操作,我们所需要的 DOM 元素其实以及完成了,但是所有 DOM 是如何按照我们的要求进行动起来呢?这里我们揭秘一下 Lottie 是如何让 DOM 动起来的。
"ks": { // 变换。对应AE中的变换设置
"o": { // 透明度
"a": 0,
"k": 100,
"ix": 11
},
"r": { // 旋转
"a": 0,
"k": 0,
"ix": 10
},
"p": { // 位置
"a": 0,
"k": [-167, 358.125, 0],
"ix": 2
},
"a": { // 锚点
"a": 0,
"k": [667, 375, 0],
"ix": 1
},
"s": { // 缩放
"a": 0,
"k": [100, 100, 100],
"ix": 6
}
}
上面这个对象是 layer 中的 ks 属性,ks 对应 AE 中 图层的变换属性,可以通过设置锚点、位置、旋转、缩放、透明度等来控制图层,并设置这些属性的变换曲线,来实现动画。lottie-web 会把 ks 处理成 transform 的属性,用于对元素进行变换操作。transform 包含了 translate(平移)、scale(缩放)、rotate(旋转)、skew(倾斜)等几种。lottie-web 中处理 ks(变换)的相关代码为:
function getTransformProperty(elem,data,container){ // data为ks数据
return new TransformProperty(elem,data,container);
}
function TransformProperty(elem,data,container){
// ...
if(data.sk){ // 获取skew相关的参数
this.sk = PropertyFactory.getProp(elem, data.sk, 0, degToRads, this);
this.sa = PropertyFactory.getProp(elem, data.sa, 0, degToRads, this);
}
if(data.a) { // 获取translate相关的参数
this.a = PropertyFactory.getProp(elem,data.a,1,0,this);
}
if(data.s) { // 获取scale相关的参数
this.s = PropertyFactory.getProp(elem,data.s,1,0.01,this);
}
}
function applyToMatrix(mat) { //使用transfrom 属性实现动画
...
if (this.a) {
mat.translate(-this.a.v[0], -this.a.v[1], this.a.v[2]);
}
if (this.s) {
mat.scale(this.s.v[0], this.s.v[1], this.s.v[2]);
}
if (this.sk) {
mat.skewFromAxis(-this.sk.v, this.sa.v);
}
if (this.r) {
mat.rotate(-this.r.v);
} else {
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]);
}
if (this.data.p.s) {
if (this.data.p.z) {
mat.translate(this.px.v, this.py.v, -this.pz.v);
} else {
mat.translate(this.px.v, this.py.v, 0);
}
} else {
mat.translate(this.p.v[0], this.p.v[1], -this.p.v[2]);
}
}
以上就是 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原生动画
欢迎自荐投稿,前端早读课等你来