引言
前端开发者根据UI视觉稿实现页面效果时一直存在这样的一种“矛盾” - 动画效果更完美与工期成本的矛盾。一般来说,页面中包含的动画效果越复杂,前端程序员在实现时需要的工期成本越大,尤其是在官网、大促活动、活动拉新等包含巨多动画效果的场景中,动画实现需要的时间占据了大部分工期时间,而工期往往是非常紧凑的。同时后期还经常伴随着与UI设计师的反复“拉扯” - 动画方向要改变下,运动的路径曲线要再调整下,这个圆角值不太圆,这个图片辛苦再替换下。诸如此类,都是让程序员脑袋变秃的罪魁祸首之一!
那么到底有没有一种方式能够打破如上的困境呢?一批批的开发者们就在不停的思索,将动画的设计实现与使用进行物理分割,让专业的人做更专业的事儿!
-
产出某种物料:让对色彩、交互更专业的UI同学完成动画的设计、实现、优化,产出一种可供识别的物料;
-
以某种形式直接识别、使用物料:前端研发通过某种形式直接识别该物料,调用后页面直接渲染动画,无须再配置路径动画、描边动画等;
在这样的背景下,经过不断思考与探索,Lottie动画应运而生!
lottie认识与使用
Lottie 认识
Lottie 是 Airbnb 开源的一套跨平台的完整的动画效果解决方案,用过都说好。完全解耦开发人员和设计师,让设计师设计的动画,在程序中无缝还原。
Lottie是一款由airbnb开源的跨平台动画渲染库,支持Android, iOS, Web, Windows平台。是专门用于解析从AE(Adobe After Effects)中通过Bodymovin插件导出的JSON文件,直接渲染动画。动画文件通过AE的bodymovin插件转换成通用的json格式描述文件后,应用开发者只需使用Lottie解析json文件,就能将动画绘制出来。
绘制过程
首先基于绘图软件AE(After Effects) 进行动画制作,在AE上完成动画后,将 输出 通过AE上的插件,转化成 Json格式。这就有点像SVG矢量图,它既可以是个svg格式的图片,又可以变成 代码的形式让程序进行操控其属性(颜色、路径等等)
Lottie解析json格式的动画描述文件后,会基于canvas 画布进行2D渲染,并结合原生组件Animator实现动画效果。
详细流程如下:
-
动画加载准备,在使用Lottie加载动画前需先通过插件 bodymovin 将AE生成的动画文件转换为通用的 json 格式描述文件
-
获取json文件中的动画数据,进行渲染前的准备与初始化。
-
解析json文件中的动画数据,包括 fr:帧率、asset:图片资源信息集合、layers:图层集合
-
创建动画实例,设置动画信息。
-
初始化布局宽高,设置绘制样式等信息。
-
启动动画,触发逐帧绘制。
-
更新动画进度和相关初始化完成周期函数回调。
-
返回动画实例,通过loadAnimation()接口返回动画实例AnimationItem。
-
控制动画,Lottie提供了一整套简洁易用API,如停止stop()、暂停pause()、播放play()、播放流转togglePause()、方向setDirection()、速度setSpeed()等。
由上面的动画绘制流程可知,lottie动画的关键在用解析json结构,那么这个json文件到底有哪些什么之处呢?我们接着往下探索。
JSON 文件神秘面纱
我们可以随机将某个动画的json进行格式化,可以比较直观的观察它的结构层次,如下图所示:
Lottie动画Json结构 可大致分为4层
-
结构层:可以读取到动画画布的宽高,帧数,背景色,时间,起始关键帧,结束帧等。
-
asset:图片资源信息集合,这里放置的是 制作动画时引用的图片资源。
-
layers:图层集合,这里可以获取到多少图层,每个图层的开始帧 结束帧等。
-
.shapes:元素集合,可以获取到每个图层都包含多个动画元素。
最终在借助canvas、svg等能力渲染得到了炫酷的动画效果。
基本使用
1、安装基础依赖
npm install lottie-web --save
2、动画初始化
import lottie from 'lottie-web'
const animation = lottie.loadAnimation({
container: document.getElementById('box'),
renderer: 'svg',// 渲染方式:svg、canvas
loop: true, // 循环播放,默认:false
autoplay: true, //自动播放 ,默认:true
path: ' ' // json 路径
})
3、动画常用方法
animation.play(); // 播放,从当前帧开始播放
animation.stop(); // 停止,并回到第0帧
animation.pause(); // 暂停,并保持当前帧
animation.goToAndStop(value, isFrame); // 跳到某个时刻/帧并停止isFrame(默认false)指示value表示帧还是时间(毫秒)
animation.goToAndPlay(value, isFrame); // 跳到某个时刻/帧并进行播放
animation.goToAndStop(30, true); // 跳转到第30帧并停止
animation.goToAndPlay(300); // 跳转到第300毫秒并播放
animation.playSegments(arr, forceFlag); // arr可以包含两个数字或者两个数字组成的数组,forceFlag表示是否立即强制播放该片段
animation.playSegments([10, 20], false); // 播放完之前的片段,播放10-20帧
animation.playSegments([[0, 5], [10, 18]], true); // 直接播放0-5帧和10-18帧
animation.setSpeed(speed); // 设置播放速度,speed为1表示正常速度
animation.setDirection(direction); // 设置播放方向,1表示正向播放,-1表示反向播放
animation.destroy(); // 删除该动画,移除相应的元素标签等。在unmount的时候,需要调用该方法
动态修改动画
目前,lottie动画已经比较完美的实现了设计师的炫酷动画效果,但随之而来也有了新的问题。
-
第一,lottie动画需要耗费设计师较大的工作时间去产出json,怎么能够基于某种方式将一种动画使用于不同的场景呢?从而提高效率;
-
第二,怎么将动画中的静态内容与业务数据结合,文案根据不同的用户层级呈现出不同的业务数据呢?
带着上面的疑问我们接着寻找答案。
动态修改 Lottie 中的文本
比如在某次运营活动中,有个每天打卡得奖励的活动,每天打卡后我们展示一个动画,提醒用户举例获取奖励还有几天,从而激励用户每天打卡。为了实现这个效果肯定不能让设计师产出10套动画json文件吧,理想情况下是不是动态去替换这里的文本内容更好呢?
要实现 Lottie 的文本动态修改,需要对 Lottie 的运行机制有一定了解
简单来说,运行前设计师导出的 lottie.json 对象,会被 lottie-web 解析之后产生相应的 JS 对象,并在动画播放期间,由 JS 对象计算并修改 HTML 中相应的 svg 元素属性,从而实现动画播放。因此如果要做文本动态修改,理论上在这 几 个阶段都是可以「动手脚」。
**1、**修改 lottie.json
lottie.json 内部描述了动画的所有细节,自然也就包含了动画中的那段文本,如果我们能找到相应的字段进行修改,也就可以实现文本替换了,上面的json可以找到如下内容:
那么我们是不是在获取到业务数据后,是不是就可以动态替换里面的占位符,从而渲染真实的业务数据呢。
fetch('xxx.json')
.then(resp => resp.text())
.then(text => {
// 简单演示替换
const newJSON = text.replace('${days}', '3');
lottie.loadAnimation({
animationData: JSON.parse(newJSON),
container: document.getElementById('app'),
loop: true
});
});
替换后的效果:
这种方式简单明了容易实现,但是也有如下的缺点:
-
文本替换存在一定不确定性,比如上面例子如果不是设计师在 AE 中写入
${days}这样明确的占位符,这个方法可就没那么容易实现了,如果设计师配合度不高,那么这个方式可操作性就差了一些. -
这种方式无法做到「运行时」修改,也就是 lottie 解析播放后就无法再修改文本了,比如某些场景下可能需要先播放 lottie 的前一部分,用户交互产生数据后,再替换文本播放后一部分,那么这种方式就无法满足了.
**2、**修改 JS 对象
针对「运行时」的情景,是不是通过修改 lottie 解析后的运行时 JS 对象,理论上一样可以修改文本。通过不断尝试和调研发现,lottie动画创建返回的实例对象中有个特殊的方法:getKeyPath。比如如下动画,如何利用getKeyPath替换指定的文本内容呢,
方法“getKeyPath”是Lottie-web库中的一个函数,其作用是返回符合指定名称的所有属性路径。动画文件中的属性路径是指导出文件中存在的层次结构路径,每一层级都有一个名称或标记。通俗来说就是我们可以找到指定的对象节点,我们看下渲染后的节点是什么样子
要替换的金额部分,渲染的dom节点有个id为 "amount" 的唯一标识,那么我们就可以通用getkeyPath通过这个唯一id获取到对象,最终通过js修改的方式如下:
anim.addEventListener("DOMLoaded", () => {
const api = lottie_api.createAnimationApi(anim);
const elements = api.getKeyPath("#amount"); // 查找对象
elements.getElements()[0].setText("1.02");
});
getkeyPath不仅可以通过id标识查找到节点,还可以通过".calssName" 查找。elements.getElements() 可以获取到所有满足条件的 JS 对象,因为 lottie 中图层的名称并不唯一,有时候可能会找到多个对象就需要有一定的约定去规范这种图层命名的方式。
这种方法相比第一种要繁琐一些,但是我更偏好这种,因为能够做到「运行期」替换
另外如果文本图层存在多个关键帧的话,setText() 方法可以传入第二个入参替换指定关键帧中的文案。缺点是图层命名需要有一定的规范,同时需要了解如何找到这个图层的名称。
小结
动画由设计使用专业的动画制作工具Adobe After Effects来实现,使动画实现更加方便,动画效果也更好;前端可以方便的调用动画,并对动画进行控制,减少前端动画工作量。但是这种动画更多的是静态的动画展示,在动画交互和动态修改上面提供的操作方式有限,上面我们只是介绍了如何动态修改文本,后续会为大家介绍动态修改图片资源、交互hack等内容。
为了满足大家的好奇心,传送两个比较高级的lottie交互动画满足大家的好奇心。
参考链接: