Jansiel Notes

Lottie-web 实践与应用

前言

  • 上一期根据源码对 lottie 做了分析,今天我们结合 lottie-web 文档来说具体的使用和开发中遇到过的坑。
  • 我的初步思路是这样的,开发一个基于 lottie-web 的 react 基础组件,把动画组件作为一个子组件放入弹窗组件中去应用。接下来我们就开始造轮子。

Lottie-web 使用和常用方法

我们先来讲讲 lottie 的常用方法。

基本用法

1const anim = window.bodymovin.loadAnimation({
2  container: element, // 挂载动画的容器dom元素
3  renderer: \'svg\', // 渲染方式,svg、canvas、html(轻量版仅svg渲染)
4  loop: true, // 是否循环播放
5  autoplay: true, // 是否自动播放
6  path: animJsonPath, // 动画json文件路径
7});

常用方法

lottie-web 提供了很多控制动画的方法,我们沿用上面的animation对象来演示。

 1 
 2anim.play(); // 播放动画,从目前停止的帧开始播放
 3anim.stop(); // 停止播放动画,回到第0帧
 4anim.pause(); // 暂停动画,在当前帧停止并保持
 5
 6anim.goToAndStop(value, isFrame); // 跳到某个时刻/帧并停止。isFrame(默认false)指示value表示帧还是时间(毫秒)
 7anim.goToAndPlay(value, isFrame); // 跳到某个时刻/帧并进行播放
 8anim.goToAndStop(20, true); // 跳转到第20帧并停止
 9anim.goToAndPlay(200); // 跳转到第200毫秒并播放
10
11anim.playSegments(arr, forceFlag); // arr可以包含两个数字或者两个数字组成的数组,forceFlag表示是否立即强制播放该片段
12anim.playSegments([10,30], false); // 播放完之前的片段,播放10-30帧
13anim.playSegments([[0,10],[20,30]], true); // 直接播放0-10帧和20-30帧
14
15anim.setSpeed(speed); // 设置播放速度,speed为1表示正常速度
16anim.setDirection(direction); // 设置播放方向,1表示正向播放,-1表示反向播放
17anim.destroy(); // 销毁动画,移除相应的元素标签等。在unmount的时候,需要调用该方法

常用事件

我们在 lottie-web 中可能也需要监听一些事件,比如动画相关的 dom 已经被添加到 html 后触发 的DOMLoaded事件。监听方法如下:

1anim.addEventListener(\'DOMLoaded\', () => {
2    if (typeof callback === \'function\') {
3      callback(anim); // 处理动画内交互逻辑
4    }
5});

除了DOMLoaded事件,下面还有一些其他常用的事件可以监听:

  • complete: 播放完成(循环播放下不会触发)
  • loopComplete: 当前循环下播放(循环播放/非循环播放)结束时触发
  • enterFrame: 每进入一帧就会触发,播放时每一帧都会触发一次,stop 方法也会触发
  • segmentStart: 播放指定片段时触发,playSegments、resetSegments 等方法刚开始播放指定片段时会发出,如果 playSegments 播放多个片段,多个片段最开始都会触发。
  • data_ready: 动画 json 文件加载完毕触发
  • DOMLoaded: 动画相关的 dom 已经被添加到 html 后触发
  • destroy: 将在动画删除时触发

JSON数据动态更新

要实现 Lottie 的文本动态修改,需要对 Lottie 的运行机制有一定了解,简单来说, lottie-web 解析 JSON 之后产生相应的 JS 对象,并在动画播放期间,由 JS 对象计算并修改 HTML 中相应的 svg 元素属性,从而实现动画播放,这里我们只针对 SVG 类型说明。lottie原理可参考结合Lottie-web源码的深入分析

graph LR
loadAnimation --> AnimationItem --> SVG

react-lottie 实现源码

考虑到通用性,我需要考虑传入存在可变的配置参数,比如动画 JSON、控制动图弹窗、回调函数等。

 1import React, { useEffect, useRef } from \'react\';
 2import lottie from \'lottie-web\';
 3
 4export interface LottieType {
 5 animJson: any; // 动画 JSON
 6 setShow: Function; // 控制弹窗的显示
 7 showMark?: boolean; // 多层动画时,控制弹层蒙层
 8 callback: Function; // 回调函数
 9}
10
11const Lottie = (props: LottieType) => {
12 const {
13   animJson,
14   setShow, // 控制关闭
15   callback,
16   showMark = false, // 控制显示蒙层
17 } = props;
18 const lottieRef = useRef(null);
19 let anim = null;
20 useEffect(() => {
21   if (lottieRef && lottieRef.current && !showMark) {
22     lottieRef.current.parentElement.parentElement.parentElement.parentElement.parentElement.previousElementSibling.remove();
23   }
24   if (animJson) {
25     anim = lottie.loadAnimation({
26       container: lottieRef.current, // the dom element that will contain the animation
27       renderer: \'svg\',
28       loop: false,
29       autoplay: false,
30       animationData: animJson,
31       rendererSettings: {
32         progressiveLoad: true,
33         preserveAspectRatio: \'xMidYMid slice\',
34         imagePreserveAspectRatio: \'xMidYMid slice\',
35       },
36     });
37     anim.addEventListener(\'DOMLoaded\', () => {
38       if (typeof callback === \'function\') {
39         callback(anim); // 处理动画内交互逻辑
40       }
41     });
42     anim.onComplete = () => {
43       setShow(false);
44     };
45   }
46   return () => {
47     anim && anim.destroy();
48   };
49 }, []);
50 return <div className=\"lottie-container\" ref={lottieRef}></div>;
51};
52
53export default Lottie;

动态更新动画 JSON 的基类

animationActionBase.m.ts基类里提供动画操作的类型接口、加载动画的基类方法以及动态修改动画 JSON 内容的方法。

代码如下:

 1 
 2/**
 3 * 基类动画接口
 4 */
 5export interface AnimationActionBaseType {
 6  initPopLoad: Function; // 初始化动画弹窗和动画组件
 7  complete?: Function; // 自动以完成动画后操作
 8  loadDataPreUpdate: Function; // load动画数据前更新数据,返回修改后的数据
 9  loadedAnimationCallBack: Function; // 加载完动画后操作
10}
11/**
12 * 加载动画基类
13 * @param props
14 *  initPopLoad: 初始化动画弹窗和动画组件
15    complete?: 自动以完成动画后操作
16    loadDataPreUpdate: load动画数据前更新数据,返回修改后的数据
17    loadedAnimationCallBack, 加载完动画后操作
18 */
19export const animationActionBase = (props: AnimationActionBaseType) => {
20  const {
21    initPopLoad,
22    complete = null,
23    loadDataPreUpdate,
24    loadedAnimationCallBack,
25  } = props;
26  if (typeof loadDataPreUpdate === \'function\') {
27    const data = loadDataPreUpdate();
28    const callback = (anim) => {
29      if (typeof loadedAnimationCallBack === \'function\')
30        loadedAnimationCallBack(anim);
31      if (typeof complete === \'function\') complete(anim); // 手动控制完成后的操作
32    };
33    if (typeof initPopLoad === \'function\') initPopLoad(data, callback); // 初始化渲染弹窗
34  }
35};
36
37/**
38 * 修改动画图层layers中的文案
39 * @param layers layers object
40 * @param index  下标
41 * @param value  内容
42 */
43export const setLayersText = (layers, index, value) => {
44  layers[index].nm = layers[index].t.d.k[0].s.t = value;
45};
46/**
47 * 修改动画assets里的图片数据
48 * @param assets assets object
49 * @param index  下标
50 * @param imgUrl  图标地址  不修改地址传 null
51 * @param imgFile 图片名称(带扩展名)
52 */
53export const setAssetImg = (assets, index, imgUrl, imgFile) => {
54  if (imgUrl) assets[index].u = imgUrl;
55  assets[index].p = imgFile;
56};
57
58/**
59 * 批量更新assets里的图片 URL
60 * @param assets assets object
61 * @param imgUrl 图片 URL
62 * @param version 版本清理 CDN 缓存
63 */
64export const setAssetListImgUrl = (assets, imgUrl, version = 1) => {
65  assets.forEach((item) => {
66    item.u = imgUrl;
67    item.p = item.p.split(\'?\')[0] + `?v=${version}`;
68  });
69};

参考文献