Lottie如何渲染大JSON动画

微信图片_2026-05-09_204924_068

Lottie是 Airbnb 发布的一款开源动画库,它适用于 Android、iOS、Web 和 Windows 的库。 它提供了一套从设计师使用 AE(Adobe After Effects)到各端开发者实现动画的工具流。

组件

项目中的lottie动画组件\mova_app_mall\src\pagesC\components\c-lottie\c-lottie.vue

多个动画、动画过大如何使用该组件?

  1. 动画传值

方式1:把JSON文件放在oss上(cdn),直接向组件传参数:src=”lottieSrc”

方式2:把JSON文件(小于1M)放在oss上,请求并缓存到storage中,要使用的时候,从storage获取,并向组件传参数:animationData=”animationData”

  1. 单个动画

通常采用src的方式传参

JavaScript
<cLottie
    :class="{ offsetScreen: ifCanvasOffsetScreen }"
    ref="cLottieRef"
    :src="lottieSrc"
    @Complete="onPrizeAnimationComplete"
    width="1630rpx"
    height="1630rpx"
    :loop="false"
    @EnterFrame="EnterFrame"
></cLottie>


mounted() {
    this.lottieSrc = Vue.prototype.$preGiftAnimate1;
    // 预加载动画
    uni.request({ url: this.lottieSrc }).then(() => {
        // 开始动画
        this.animateInitTimer = setTimeout(() => {
            const animationTimer = setTimeout(() => {
                const cLottieRef = this.$refs.cLottieRef;
                cLottieRef?.call('goToAndStop', [0, false]);
                cLottieRef?.call('play');
                this.showContainerTimer = setTimeout(
                    () => {
                        // 这里可以做业务逻辑,如显示一个弹窗或者其他逻辑,(2000)时间根据实际情况调整
                    },
                    2000
                );
                clearTimeout(animationTimer);
            }, 300);
            clearTimeout(this.animateInitTimer);
        }, 1000);
    });

    <em>// 容错,2s后还没开始播放动画的话就直接显示</em>
    const isAnimateStartTimer = setTimeout(() => {
        if (!this.isAnimateStart) {
            // 这里可以做业务逻辑,如显示一个弹窗或者其他逻辑
        }
        clearTimeout(isAnimateStartTimer);
    }, 2000);
},

// 一个动画播放结束回调
onPrizeAnimationComplete() {
    this.ifCanvasOffsetScreen = true;
    console.log('播放完毕?');
},
// 开始动画回调
EnterFrame() {
    this.isAnimateStart = true;
},
  1. 分片动画(多个动画)

多个动画的时候,通常采用缓存多个不超过1M的JSON的方式,用animationData传参

JavaScript
<c-lottie
    :class="{ offsetScreen: ifCanvasOffsetScreen }"
    ref="cLottieRef"
    :src="lottieSrc"
    @Complete="onPrizeAnimationComplete"
    width="1630rpx"
    height="1630rpx"
    :loop="false"
    :animationData="animationData && animationData.length && animationData[currentLottieIndex].data"
    @dataFailed="onDataFailed"
    @EnterFrame="EnterFrame"
></c-lottie>

data() {
    return {
        currentLottieIndex: 0,
    }
}

mounted() {
    // 可以用lottieSrc兜底,防止取不到storage
    if (
        uni.getStorageSync('preGiftAnimate11') &&
        uni.getStorageSync('preGiftAnimate12') &&
        uni.getStorageSync('preGiftAnimate13')
    ) {
        this.animationData = [
            { data: uni.getStorageSync('preGiftAnimate11'), showContainer: false },
            { data: uni.getStorageSync('preGiftAnimate12'), showContainer: false },
            { data: uni.getStorageSync('preGiftAnimate13'), showContainer: true },
        ];
    } else {
        this.lottieSrc = Vue.prototype.$preGiftAnimate1;
    }
    
    if (this.lottieSrc) {
        // 预加载动画
        uni.request({ url: this.lottieSrc }).then(() => {
            // 开始动画
            this.animateInitTimer = setTimeout(() => {
                const animationTimer = setTimeout(() => {
                    const cLottieRef = this.$refs.cLottieRef;
                    cLottieRef?.call('goToAndStop', [0, false]);
                    cLottieRef?.call('play');
                    this.showContainerTimer = setTimeout(
                        () => {
                            // 这里可以做业务逻辑,如显示一个弹窗或者其他逻辑,(2000)时间根据实际情况调整
                        },
                        2000
                    );
                    clearTimeout(animationTimer);
                }, 300);
                clearTimeout(this.animateInitTimer);
            }, 1000);
        });
    } else {
        // 开始动画
        this.animateInitTimer = setTimeout(() => {
            const animationTimer = setTimeout(() => {
                const cLottieRef = this.$refs.cLottieRef;
                cLottieRef?.call('goToAndStop', [0, false]);
                cLottieRef?.call('play');
                clearTimeout(animationTimer);
            }, 300);
            clearTimeout(this.animateInitTimer);
        }, 800);
    }
    
    // 容错,2s后还没开始播放动画的话就直接显示
    const isAnimateStartTimer = setTimeout(() => {
        if (!this.isAnimateStart) {
            // 这里可以做业务逻辑,如显示一个弹窗或者其他逻辑
        }
        clearTimeout(isAnimateStartTimer);
    }, 2000);
}
// 一个动画播放结束回调
onPrizeAnimationComplete() {
    this.ifCanvasOffsetScreen = true;
    console.log('播放完毕?');
    if (!this.lottieSrc) {
        if (this.currentLottieIndex < this.animationData.length - 1) {
            // 加载下一个分片动画
            this.currentLottieIndex++;
            const animationTimer = setTimeout(() => {
                const cLottieRef = this.$refs.cLottieRef;
                cLottieRef?.call('goToAndStop', [0, false]);
                cLottieRef?.call('play');
                if (this.animationData[this.currentLottieIndex].showContainer) {
                    this.showContainerTimer = setTimeout(() => {
                        // 这里可以做业务逻辑,如显示一个弹窗或者其他逻辑,(200)时间根据实际情况调整
                        clearTimeout(this.showContainerTimer);
                    }, 200);
                }
                clearTimeout(animationTimer);
            }, 300);
        }
    }
},
// 开始动画回调
<strong>EnterFrame</strong>() {
    this.isAnimateStart = true;
},
  1. 预加载动画/动画缓存/兜底容错

全局预加载/storage缓存:

JavaScript
// 全局预加载,在项目入口文件main.ts中预加载/storage缓存
const <strong>preloadRemoteConfig</strong> = async () => {
    const json11 = `https://shop-oss.iot.mova-tech.com/mova_app_mall/activity/PreGift/preGift-pop-animate-1.json?t=${Date.now()}/data.json`;
    const json12 = `https://shop-oss.iot.mova-tech.com/mova_app_mall/activity/PreGift/preGift-pop-animate-2.json?t=${Date.now()}/data.json`;
    const json13 = `https://shop-oss.iot.mova-tech.com/mova_app_mall/activity/PreGift/preGift-pop-animate-3.json?t=${Date.now()}/data.json`;
    const json1 = `https://shop-oss.iot.mova-tech.com/mova_app_mall/activity/PreGift/preGift-pop-animate.json?t=${Date.now()}/data.json`;
    uni.request({
        url: json11,
        <strong>success</strong>: (<em>res</em>) => {
            if (<em>res</em>.statusCode === 200 && <em>res</em>.data) {
                try {
                    const jsonStr = JSON.stringify(<em>res</em>.data);
                    uni.setStorageSync('preGiftAnimate11', jsonStr);
                    console.log({ title: 'JSON 缓存成功' });
                } catch (e) {
                    console.log({ title: '缓存失败', icon: 'none' });
                    console.error('缓存 JSON 出错:', e);
                }
            }
        },
        <strong>fail</strong>: (<em>err</em>) => {
            console.log({ title: '请求 JSON 失败', icon: 'none' });
            console.error('请求远程 JSON 出错:', <em>err</em>);
        },
    });
    uni.request({
        url: json12,
        <strong>success</strong>: (<em>res</em>) => {
            if (<em>res</em>.statusCode === 200 && <em>res</em>.data) {
                try {
                    const jsonStr = JSON.stringify(<em>res</em>.data);
                    uni.setStorageSync('preGiftAnimate12', jsonStr);
                    console.log({ title: 'JSON 缓存成功' });
                } catch (e) {
                    console.log({ title: '缓存失败', icon: 'none' });
                    console.error('缓存 JSON 出错:', e);
                }
            }
        },
        <strong>fail</strong>: (<em>err</em>) => {
            console.log({ title: '请求 JSON 失败', icon: 'none' });
            console.error('请求远程 JSON 出错:', <em>err</em>);
        },
    });
    uni.request({
        url: json13,
        <strong>success</strong>: (<em>res</em>) => {
            if (<em>res</em>.statusCode === 200 && <em>res</em>.data) {
                try {
                    const jsonStr = JSON.stringify(<em>res</em>.data);
                    uni.setStorageSync('preGiftAnimate13', jsonStr);
                    console.log({ title: 'JSON 缓存成功' });
                } catch (e) {
                    console.log({ title: '缓存失败', icon: 'none' });
                    console.error('缓存 JSON 出错:', e);
                }
            }
        },
        <strong>fail</strong>: (<em>err</em>) => {
            console.log({ title: '请求 JSON 失败', icon: 'none' });
            console.error('请求远程 JSON 出错:', err);
        },
    });
    uni.request({ url: json1 });
    Vue.prototype.$preGiftAnimate11 = json11;
    Vue.prototype.$preGiftAnimate12 = json12;
    Vue.prototype.$preGiftAnimate13 = json13;
    Vue.prototype.$preGiftAnimate1 = json1;
};
preloadRemoteConfig();

兜底容错:

JavaScript
// 1. 动画开始的时候标记动画一开始
// 开始动画回调
<strong>EnterFrame</strong>() {
    this.isAnimateStart = true;
},

// 在初始化动画的时候加上容错
// 容错,2s后还没开始播放动画的话就直接显示
const isAnimateStartTimer = setTimeout(() => {
    if (!this.isAnimateStart) {
        // 这里可以做业务逻辑,如显示一个弹窗或者其他逻辑
    }
    clearTimeout(isAnimateStartTimer);
}, 2000);

注意事项

JSON体积,一般不超过2M,动画实在太大的时候,可以考虑让UI【压缩】或者【切成多个JSON(小于1M),再由前端拼接】
注意预加载/缓存动画/容错兜底,防止出现动画加载不出来的情况,同时引发后续业务逻辑的阻断(如业务弹窗等)
缓存动画的时候,注意小程序缓存的json的体积不能大于1M,否则真机上会报错
width=”1630rpx” height=”1630rpx”,按照实际动画来手动调整

评论

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注