前言
上一期根据源码对 lottie 做了分析,今天我们结合 lottie-web 文档来说具体的使用和开发中遇到过的坑。
我的初步思路是这样的,开发一个基于 lottie-web 的 react 基础组件,把动画组件作为一个子组件放入弹窗组件中去应用。接下来我们就开始造轮子。
Lottie-web 使用和常用方法
我们先来讲讲 lottie 的常用方法。
基本用法
const anim = window.bodymovin.loadAnimation({
container: element, // 挂载动画的容器dom元素
renderer: 'svg', // 渲染方式,svg、canvas、html(轻量版仅svg渲染)
loop: true, // 是否循环播放
autoplay: true, // 是否自动播放
path: animJsonPath, // 动画json文件路径
});
常用方法
lottie-web 提供了很多控制动画的方法,我们沿用上面的animation对象来演示。
anim.play(); // 播放动画,从目前停止的帧开始播放
anim.stop(); // 停止播放动画,回到第0帧
anim.pause(); // 暂停动画,在当前帧停止并保持
anim.goToAndStop(value, isFrame); // 跳到某个时刻/帧并停止。isFrame(默认false)指示value表示帧还是时间(毫秒)
anim.goToAndPlay(value, isFrame); // 跳到某个时刻/帧并进行播放
anim.goToAndStop(20, true); // 跳转到第20帧并停止
anim.goToAndPlay(200); // 跳转到第200毫秒并播放
anim.playSegments(arr, forceFlag); // arr可以包含两个数字或者两个数字组成的数组,forceFlag表示是否立即强制播放该片段
anim.playSegments([10,30], false); // 播放完之前的片段,播放10-30帧
anim.playSegments([[0,10],[20,30]], true); // 直接播放0-10帧和20-30帧
anim.setSpeed(speed); // 设置播放速度,speed为1表示正常速度
anim.setDirection(direction); // 设置播放方向,1表示正向播放,-1表示反向播放
anim.destroy(); // 销毁动画,移除相应的元素标签等。在unmount的时候,需要调用该方法
常用事件
我们在 lottie-web 中可能也需要监听一些事件,比如动画相关的 dom 已经被添加到 html 后触发
的DOMLoaded事件。监听方法如下:
anim.addEventListener('DOMLoaded', () => {
if (typeof callback === 'function') {
callback(anim); // 处理动画内交互逻辑
}
});
除了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源码的深入分析。
react-lottie 实现源码
考虑到通用性,我需要考虑传入存在可变的配置参数,比如动画 JSON、控制动图弹窗、回调函数等。
import React, { useEffect, useRef } from 'react';
import lottie from 'lottie-web';
export interface LottieType {
animJson: any; // 动画 JSON
setShow: Function; // 控制弹窗的显示
showMark?: boolean; // 多层动画时,控制弹层蒙层
callback: Function; // 回调函数
}
const Lottie = (props: LottieType) => {
const {
animJson,
setShow, // 控制关闭
callback,
showMark = false, // 控制显示蒙层
} = props;
const lottieRef = useRef(null);
let anim = null;
useEffect(() => {
if (lottieRef && lottieRef.current && !showMark) {
lottieRef.current.parentElement.parentElement.parentElement.parentElement.parentElement.previousElementSibling.remove();
}
if (animJson) {
anim = lottie.loadAnimation({
container: lottieRef.current, // the dom element that will contain the animation
renderer: 'svg',
loop: false,
autoplay: false,
animationData: animJson,
rendererSettings: {
progressiveLoad: true,
preserveAspectRatio: 'xMidYMid slice',
imagePreserveAspectRatio: 'xMidYMid slice',
},
});
anim.addEventListener('DOMLoaded', () => {
if (typeof callback === 'function') {
callback(anim); // 处理动画内交互逻辑
}
});
anim.onComplete = () => {
setShow(false);
};
}
return () => {
anim && anim.destroy();
};
}, []);
return <div className="lottie-container" ref={lottieRef}></div>;
};
export default Lottie;
动态更新动画 JSON 的基类
animationActionBase.m.ts基类里提供动画操作的类型接口、加载动画的基类方法以及动态修改动画 JSON 内容的方法。
代码如下:
/**
* 基类动画接口
*/
export interface AnimationActionBaseType {
initPopLoad: Function; // 初始化动画弹窗和动画组件
complete?: Function; // 自动以完成动画后操作
loadDataPreUpdate: Function; // load动画数据前更新数据,返回修改后的数据
loadedAnimationCallBack: Function; // 加载完动画后操作
}
/**
* 加载动画基类
* @param props
* initPopLoad: 初始化动画弹窗和动画组件
complete?: 自动以完成动画后操作
loadDataPreUpdate: load动画数据前更新数据,返回修改后的数据
loadedAnimationCallBack, 加载完动画后操作
*/
export const animationActionBase = (props: AnimationActionBaseType) => {
const {
initPopLoad,
complete = null,
loadDataPreUpdate,
loadedAnimationCallBack,
} = props;
if (typeof loadDataPreUpdate === 'function') {
const data = loadDataPreUpdate();
const callback = (anim) => {
if (typeof loadedAnimationCallBack === 'function')
loadedAnimationCallBack(anim);
if (typeof complete === 'function') complete(anim); // 手动控制完成后的操作
};
if (typeof initPopLoad === 'function') initPopLoad(data, callback); // 初始化渲染弹窗
}
};
/**
* 修改动画图层layers中的文案
* @param layers layers object
* @param index 下标
* @param value 内容
*/
export const setLayersText = (layers, index, value) => {
layers[index].nm = layers[index].t.d.k[0].s.t = value;
};
/**
* 修改动画assets里的图片数据
* @param assets assets object
* @param index 下标
* @param imgUrl 图标地址 不修改地址传 null
* @param imgFile 图片名称(带扩展名)
*/
export const setAssetImg = (assets, index, imgUrl, imgFile) => {
if (imgUrl) assets[index].u = imgUrl;
assets[index].p = imgFile;
};
/**
* 批量更新assets里的图片 URL
* @param assets assets object
* @param imgUrl 图片 URL
* @param version 版本清理 CDN 缓存
*/
export const setAssetListImgUrl = (assets, imgUrl, version = 1) => {
assets.forEach((item) => {
item.u = imgUrl;
item.p = item.p.split('?')[0] + `?v=${version}`;
});
};
参考文献
- lottie-web: github.com/airbnb/lot.…