掰掰 Lottie

您所在的位置:网站首页 animate怎么导出gif动画 掰掰 Lottie

掰掰 Lottie

2023-06-08 08:16| 来源: 网络整理| 查看: 265

前言

之前做运营活动的时候,写了一个比较有趣的打开盲盒的交互动画,流程如下:一个挣扎的未打开的盲盒,点击出现确认弹窗,确认后集齐的5张卡片会飞入盲盒,盲盒开启弹出礼品。怎么实现的呢?其中挣扎的效果是使用的CSS3做的一个呼吸加抖动的 Animation,飞入的效果是使用JS获取到盲盒的中心点坐标并设置弹窗只展示5张卡片和要改变后样式,盲盒开启的过程则是一个 Lottie 动画。

打开一个盒子

如果想要在页面中实现类似的动效,那大概有以下几个方案可以选用:

CSS3动画:使用CSS3新增的属性和选择器来实现动画,也是最常见的前端动画方案 优点是实现简单,不需要太多的代码也可以实现较为复杂的动画效果缺点是不可控。比较适用于对于元素做一些形变、位移、透明度变化等,比如:按钮呼吸,文本淡出等 JavaScript动画:通过JavaScript脚本来实现动画效果 优点是非常灵活,可以交互,渲染动态数据等,可以配合CSS3做一些动画流程,比如开红包等缺点是实现较为复杂,需要编写大量的代码,动画质量受限于开发水平 GIF/APNG图片:通过引入动图来实现动画效果,APNG 相较于 GIF 支持更多的色彩,性能稍好 优点是没什么开发成本缺点是循环播放动画不可控,存在较大的性能问题。适合做小图标,比如:loading 加载 Lottie:本文的主角 优点是开发成本较低,可以实现复杂的动画效果,动画可控,性能较好缺点在需要一个很会的UI设计,动画中动态渲染数据较为麻烦

前端做动画还存在许多方案例如:Canvas、 SVG、 SVGA 等,这里不做过多介绍大家可以自行了解

什么是Lottie

Lottie 是 Airbnb 开发的一款开源的动画库,它可以把 Adobe After Effects 制作的动画导出为交互式的矢量动画,可以在 iOS、Android 和 React Native 等移动平台上使用。它通过解析用Bodymovin导出为json的AE动画,在移动端和 Web 上进行了原生渲染。能够帮助开发者快速制作出精美的动画效果,而且还可以节省大量的开发时间。

Lottie如何使用 第一步

由动画设计同学使用AE实现动画效果后通过 Bodymovin 插件导出 json 文件 或者在Lottie动画资源网站如:Lottiefiles 找到现成的动画进行编辑后导出。

对于Bodymovin插件的安装与使用可以参考该文章AE 插件 Bodymovin介绍

在实现动画时要考虑各端对于AE效果的支持,具体可参考官方文档

第二步

开发同学安装 Lottie ,根据设计同学提供的 json 文件在项目中使用

安装 lottie-web 依赖,在项目中进行引用

npm install lottie-web # 或使用pnpm pnpm add lottie-web import bodymovin from 'lottie-web'

通过以下几种方式初始化Lottie并挂载到页面中

使用Lottie的 loadAnimation 方法进行配置,其中挂载节点 container 和 json 文件路径 path 这两个是必须配置的

const bm01 = document.getElementById('bm01') bodymovin.loadAnimation({ container: bm01, // 挂载DOM animType: 'svg', // 渲染类型 loop: true, // 是否循环播放 autoplay: true, // 自动播放 path: 'data1.json' // JSON文件路径 });

使用Lottie的 registerAnimation 方法进行注册,挂载节点以入参形式传入,json 文件路径需在节点中配置data-animation-path属性

var bm02 = document.getElementById('bm02') bodymovin.registerAnimation(bm02)

使用 searchAnimations 方法进行注册,Lottie自行通过 document.querySelector 选择类名为 “lottie” 的节点进行挂载,json 文件路径需在节点中添加 data-animation-path 属性

bodymovin.searchAnimations()

另外Lottie实例也提供了许多的 Method 和 Event,可以控制动画播放、暂停、卸载等。更多API请移步官方文档

看懂JSON文件

其实 json 文件就是对于导出动画的具象描述

基本信息 { "v": "5.7.1", // Bodymovin 插件版本号 "fr": 25, // 帧率 "ip": 0, // 开始帧 "op": 20, // 结束帧 "w": 700, // 宽 "h": 600, // 高 "nm": "测试文件", // 名称 "ddd": 0, // 是否为3D "assets": [], // 静态资源 "layers": [], // 图层信息 "markers": [] // 标记 }

其中包括了一些基本信息:插件版本、画布的宽高、帧率、起始关键帧,结束帧等。从中我们不难看出这个测试动画 1000 / 25 * 20 = 800 播放一次时长为 800 毫秒,宽高比为 7 :6

assets静态资源 { "assets": [ { "id": "image_0", // 唯一标识 "w": 169, // 宽 "h": 172, // 高 "u": "images/", // 静态资源导出文件夹 "p": "img_0.png", // 文件路径 "e": 0 // 是否直接使用p作为路径 } ] }

这里是制作动画时引用到的静态资源,使用时通过唯一标识“id”进行指定

关于获取静态资源的路径,遵从以下逻辑:

e 不为 0 时直接以 p 作为路径e 为 0 时,如果初始化时配置了 assetsPath 则使用其与 p 进行拼接,若没配置则使用 u 进行拼接作为路径 function getAssetsPath(assetData, assetsPath, originalPath) { var path = ''; if (assetData.e) { path = assetData.p; } else if (assetsPath) { path = assetsPath + assetData.p; } else { path = originalPath; path += assetData.u ? assetData.u : ''; path += assetData.p; } return path; } layers图层 { "layers": [ { "ddd": 0, // 是否使用了3d "ind": 1, // 索引 "ty": 2, // 图片图层 "nm": "图层2.png", // 名称 "cl": "png", // 图片后缀 "refId": "image_0", // 在assets中的id "sr": 1, "ks": { // 需要做的变化 "o": {}, // 不透明度 "r": {}, // 旋转 "p": {}, // 位置 "a": {}, // 锚点 "s": {} // 缩放 }, "ao": 0, // 自动 "ip": 4, // 开始帧 "op": 18, // 持续帧 "st": 0, // 开始时间 "bm": 0 // 混合模式 } ] }

各个图层相关的信息,其中包括了对应元素的ORPAS变换,即为透明,旋转,位置,锚点、缩放 每一个变化的对象内部又包括了:起止时间,贝塞尔曲线的入参等描述

源码解析

以 registerAnimation 方法为例,看看Lottie的工作流程吧

function registerAnimation(element, animationData) { if (!element) { // 没传入挂载DOM直接结束 return null; } var i = 0; // 缓存注册的动画 while (i return registeredAnimations[i].animation; } i += 1; } var animItem = new AnimationItem(); setupAnimation(animItem, element); // 绑定自定义事件 animItem.setData(element, animationData); // 与 loadAnimation 的区别 return animItem; } 首先对注册的动画进行缓存,由于一个页面可能存在多个Lottie动画,因此定义了全局的变量 len 用于记录。然后实例化 AnimationItem 类setupAnimation 方法绑定 destroy、 _active、 _idle 事件,当你调用Lottie实例上的 play、 pause、 destroy 等方法时会触发调用 setData 方法从 DOM 的属性中获取配置参数再调用 setParams,如果使用 loadAnimation 注册的话则直接调用 setParams 方法在 setParams 方法中会确定渲染方式并创建对应的渲染器,将配置参数挂载到实例上。支持的渲染器有 CanvasRenderer、 HybridRenderer、 SVGRenderer,官方推荐选用 svg AnimationItem.prototype.setParams = function (params) { // 确定渲染方式并创建对应的渲染器 var animType = params.animType || 'svg'; var RendererClass = getRenderer(animType); // SVGRenderer this.renderer = new RendererClass(this, params.rendererSettings); this.imagePreloader.setCacheType(animType, this.renderer.globalData.defs); this.renderer.setProjectInterface(this.projectInterface); // 配置信息挂载到实例上 this.animType = animType; this.autoplay = 'autoplay' in params ? params.autoplay : true; this.name = params.name ? params.name : ''; // ... dataManager.loadAnimation(params.path, this.configAnimation, this.onSetupError); }; 通过 createWorker 方法生成一个worker。后续执行 assetLoader 方法通过Ajax获取 json 文件,请求完成后对 json 文件中进行检查和处理,由于 json 文件往往比较大这个过程放在worker中进行。完成后开始执行 configAnimation 方法 function loadAnimation(path, onComplete, onError) { setupWorker(); // 生成一个worker var processId = createProcess(onComplete, onError); workerInstance.postMessage({ type: 'loadAnimation', path: path, fullPath: window.location.origin + window.location.pathname, id: processId }); } // 如果当前浏览器环境支持Worker,则创建Worker function createWorker(fn) { if (window.Worker && window.Blob && getWebWorker()) { var blob = new Blob(['var _workerSelf = self; self.onmessage = ', fn.toString()], { type: 'text/javascript' }); var url = URL.createObjectURL(blob); return new Worker(url); } } 在 configAnimation 方法中主要是初始化渲染器参数并等待所需资源加载完毕后,调用renderer上的 initItems 方法初始化所有元素再调用 renderFrame 方法进行绘制,若配置了 autoplay 则会自动调用 play 方法播放动画 AnimationItem.prototype.configAnimation = function (animData) { this.totalFrames = Math.floor(this.animationData.op - this.animationData.ip); this.firstFrame = Math.round(this.animationData.ip); // 初始化渲染器参数 this.renderer.configAnimation(animData); this.assets = this.animationData.assets; this.frameRate = this.animationData.fr; this.frameMult = this.animationData.fr / 1000; this.renderer.searchExtraCompositions(animData.assets); this.markers = markerParser(animData.markers || []); this.trigger('config_ready'); // 加载静态资源 this.preloadImages(); this.loadSegments(); this.updaFrameModifier(); // 每20ms检查一次等待所有资源加载完毕 this.waitForFontsLoaded(); }; 其他 Matrix矩阵

当使用 SVGRender 进行渲染时,观察 DOM 结构发现对于元素的变化都是使用CSS函数matrix进行描述的

总的来说使用matrix函数可以代替 transform 的以下属性:斜拉(skew)、缩放(scale)、旋转(rotate)、位移(translate),这些方法都是基于matrix函数的再封装,为了易于理解

更为详细的了解可以参考这篇博客

requestAnimationFrame function resume(nowTime) { var elapsedTime = nowTime - initTime; // 时间戳差值 var i; for (i = 0; i window.requestAnimationFrame(resume); } else { _stopped = true; } }

在 lottie-web 中动画的播放是通过不断的调用requestAnimationFrame进行每一次的绘制的,我们知道requestAnimationFrame的回调函数执行次数通常是每秒60次,可以理解为最终呈现的动画是60帧的。

那么如果设计师导出的动画不是60帧的怎么办,Lottie要怎么渲染呢?

通过上边源码我们可以发现,每次渲染都会去执行 advanceTime 方法,该方法会根据每次更新的时间差值进行计算,最终得到要渲染哪一帧;

举个例子:假如设计师导出的动画为 30 帧,一秒内让一个元素位移 1000px。那么当第一次调用requestAnimationFrame后当前动画 elapsedTime 值为16.67ms,就应该渲染动画对应 16.67 / ( 1000 / 30 ) = 0.5 帧时的状态,也就是位移 1000 / 30 * 0.5 = 16.67px,以此类推。

总结

Lottie虽好,但是想要在项目落地使用并达到预期效果,还是要和设计同学有比较充分的沟通。尽量避免三种情况:动画并不复杂(使用CSS3即可达到效果)、帧动画(考虑一下SVGA)、assets中图片素材太多(json文件太大),大家有什么问题也可以多多交流,比如:json文件的压缩处理、在 Lottie 动画中插入动态数据等

参考资料 github.com/airbnb/lott…juejin.cn/post/691483…www.zhangxinxu.com/wordpress/.…


【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3