Lottie-Web 完整技术指南:让动画开发更简单高效
📚 目录
- 一、什么是 Lottie-Web
- 二、为什么选择 Lottie-Web
- 三、安装与引入
- 四、基础使用
- 五、API 详解
- 六、Vue 集成实战
- 七、高级特性
- 八、性能优化
- 九、常见问题与解决方案
- 十、最佳实践
- 十一、实际应用场景
- 十二、总结
一、什么是 Lottie-Web
1.1 Lottie 简介
Lottie 是 Airbnb 开源的一个动画库,它可以将 After Effects 动画导出为 JSON 格式,然后在 Web、iOS、Android 等平台上渲染播放。Lottie-Web 是 Lottie 的 Web 版本实现。
1.2 核心优势
- ✅ 设计师友好:设计师在 After Effects 中制作动画,导出 JSON 文件即可使用
- ✅ 体积小:相比 GIF 和视频,JSON 文件体积更小
- ✅ 可编程控制:可以通过 JavaScript API 控制播放、暂停、速度等
- ✅ 高质量:矢量动画,支持任意缩放不失真
- ✅ 跨平台:同一份 JSON 文件可在 Web、iOS、Android 使用
1.3 工作原理
After Effects 动画 ↓ (Bodymovin 插件导出) JSON 文件 (包含动画数据) ↓ (Lottie-Web 解析) Canvas/SVG 渲染 ↓ 浏览器显示动画 二、为什么选择 Lottie-Web
2.1 传统动画方案对比
| 方案 | 优点 | 缺点 |
|---|---|---|
| GIF | 兼容性好,使用简单 | 体积大,颜色有限,无法控制播放 |
| 视频 | 质量高,支持复杂动画 | 体积大,加载慢,无法精确控制 |
| CSS3 动画 | 性能好,可控性强 | 需要手写代码,复杂动画实现困难 |
| Canvas 手写 | 完全可控 | 开发成本高,维护困难 |
| Lottie | 体积小,质量高,可控性强,开发效率高 | 需要 After Effects 和插件 |
2.2 适用场景
- ✅ 加载动画(Loading)
- ✅ 页面过渡动画
- ✅ 交互动画反馈
- ✅ 品牌 Logo 动画
- ✅ 数据可视化动画
- ✅ 微交互动画
三、安装与引入
3.1 NPM 安装(推荐)
npminstall lottie-web # 或yarnadd lottie-web 3.2 CDN 引入
<!-- 通过 CDN 引入 --><scriptsrc="https://cdnjs.cloudflare.com/ajax/libs/lottie-web/5.12.2/lottie.min.js"></script>3.3 ES6 模块引入
// 完整引入import lottie from'lottie-web'// 按需引入(如果支持)import lottie from'lottie-web/build/player/lottie_light'3.4 Vue 项目引入
// main.js 或组件中import lottie from'lottie-web'// 挂载到 Vue 原型(可选)Vue.prototype.$lottie = lottie 四、基础使用
4.1 最简单的示例
<!DOCTYPEhtml><html><head><title>Lottie 基础示例</title></head><body><!-- 创建一个容器 --><divid="lottie-container"style="width: 300px;height: 300px;"></div><scriptsrc="https://cdnjs.cloudflare.com/ajax/libs/lottie-web/5.12.2/lottie.min.js"></script><script>// 加载并播放动画const animation = lottie.loadAnimation({ container: document.getElementById('lottie-container'),// 容器元素 renderer:'svg',// 渲染方式:'svg' / 'canvas' / 'html' loop:true,// 是否循环 autoplay:true,// 是否自动播放 path:'https://assets5.lottiefiles.com/packages/lf20_V9t630.json'// JSON 文件路径})</script></body></html>4.2 使用本地 JSON 文件
// 方式1:使用 path 指定文件路径const animation = lottie.loadAnimation({ container: document.getElementById('lottie-container'), renderer:'svg', loop:true, autoplay:true, path:'./animations/my-animation.json'})// 方式2:使用 animationData 直接传入 JSON 对象import animationData from'./animations/my-animation.json'const animation = lottie.loadAnimation({ container: document.getElementById('lottie-container'), renderer:'svg', loop:true, autoplay:true, animationData: animationData // 直接传入 JSON 对象})4.3 三种渲染方式对比
SVG 渲染(推荐)
const animation = lottie.loadAnimation({ container: document.getElementById('lottie-container'), renderer:'svg',// 使用 SVG 渲染 loop:true, autoplay:true, path:'./animation.json'})优点:
- 矢量图形,任意缩放不失真
- 支持 CSS 样式控制
- 文件体积相对较小
- 适合简单到中等复杂度的动画
缺点:
- 复杂动画性能可能不如 Canvas
- DOM 元素较多时可能影响性能
Canvas 渲染
const animation = lottie.loadAnimation({ container: document.getElementById('lottie-container'), renderer:'canvas',// 使用 Canvas 渲染 loop:true, autoplay:true, path:'./animation.json'})优点:
- 性能好,适合复杂动画
- 不会产生大量 DOM 元素
- 适合大量动画同时播放
缺点:
- 缩放可能失真(取决于分辨率设置)
- 不支持 CSS 样式控制
HTML 渲染
const animation = lottie.loadAnimation({ container: document.getElementById('lottie-container'), renderer:'html',// 使用 HTML 渲染 loop:true, autoplay:true, path:'./animation.json'})优点:
- 可以使用 CSS 完全控制样式
- 支持文本选择等 HTML 特性
缺点:
- 性能较差
- 兼容性不如 SVG 和 Canvas
- 不推荐用于生产环境
五、API 详解
5.1 loadAnimation() 配置选项
const animation = lottie.loadAnimation({// ========== 必需参数 ========== container: element,// DOM 元素或选择器字符串,动画容器// ========== 渲染相关 ========== renderer:'svg',// 'svg' | 'canvas' | 'html',默认 'svg'// ========== 数据源(二选一) ========== path:'path/to/animation.json',// JSON 文件路径(字符串) animationData:{},// 或直接传入 JSON 对象// ========== 播放控制 ========== loop:true,// true | false | 数字(循环次数),默认 true autoplay:true,// 是否自动播放,默认 true// ========== 性能优化 ========== rendererSettings:{// SVG 渲染设置 preserveAspectRatio:'xMidYMid meet',// SVG preserveAspectRatio clearCanvas:true,// 是否清除画布 context:null,// Canvas 上下文(Canvas 渲染时使用) progressiveLoad:false,// 是否渐进式加载 hideOnTransparent:true,// 透明区域是否隐藏// Canvas 渲染设置 className:'',// 添加的 CSS 类名 id:'',// 元素的 ID},// ========== 其他选项 ========== name:'myAnimation',// 动画名称,用于后续引用 initialSegment:[0,30],// 初始播放片段 [开始帧, 结束帧]})5.2 动画实例方法
播放控制
// 播放动画 animation.play()// 暂停动画 animation.pause()// 停止动画(回到第一帧) animation.stop()// 跳转到指定帧 animation.goToAndStop(50,true)// (帧数, 是否触发事件)// 跳转到指定帧并播放 animation.goToAndPlay(50,true)// (帧数, 是否触发事件)// 设置播放速度(1 = 正常速度,2 = 2倍速,0.5 = 0.5倍速) animation.setSpeed(2)// 设置播放方向(1 = 正向,-1 = 反向) animation.setDirection(1)// 设置循环模式 animation.setLooping(true)// true | false | 数字属性获取
// 获取动画总帧数const totalFrames = animation.totalFrames // 获取当前帧数const currentFrame = animation.currentFrame // 获取动画时长(秒)const duration = animation.duration // 获取动画帧率const frameRate = animation.frameRate // 获取动画是否正在播放const isPlaying = animation.isPaused ===false// 获取动画是否暂停const isPaused = animation.isPaused // 获取动画循环次数const loop = animation.loop 事件监听
// 动画加载完成 animation.addEventListener('DOMLoaded',()=>{ console.log('动画 DOM 加载完成')})// 数据加载完成 animation.addEventListener('data_ready',()=>{ console.log('动画数据加载完成')})// 配置加载完成 animation.addEventListener('config_ready',()=>{ console.log('动画配置加载完成')})// 动画加载失败 animation.addEventListener('data_failed',()=>{ console.log('动画加载失败')})// 动画加载完成(所有资源) animation.addEventListener('loaded_images',()=>{ console.log('所有图片资源加载完成')})// 动画进入循环 animation.addEventListener('loopComplete',()=>{ console.log('动画完成一次循环')})// 动画完成(非循环模式下) animation.addEventListener('complete',()=>{ console.log('动画播放完成')})// 动画进入指定帧 animation.addEventListener('enterFrame',()=>{ console.log('当前帧:', animation.currentFrame)})// 动画片段开始 animation.addEventListener('segmentStart',()=>{ console.log('动画片段开始')})// 销毁动画 animation.addEventListener('destroy',()=>{ console.log('动画已销毁')})其他方法
// 重新加载动画 animation.reload()// 调整大小(当容器尺寸改变时调用) animation.resize()// 更新动画数据(动态替换动画) animation.updateDocumentData(newAnimationData)// 销毁动画实例(释放资源) animation.destroy()// 获取动画版本const version = animation.version 5.3 全局方法
// 暂停所有动画 lottie.pause()// 播放所有动画 lottie.play()// 设置所有动画的播放速度 lottie.setSpeed(2)// 设置所有动画的播放方向 lottie.setDirection(1)// 停止所有动画 lottie.stop()// 根据名称获取动画实例const animation = lottie.getRegisteredAnimations()[0]// 根据名称查找动画const animation = lottie.findAnimationByID('myAnimation')// 注册动画(手动注册) lottie.registerAnimation(animation,'myAnimation')// 取消注册动画 lottie.unregisterAnimation('myAnimation')六、Vue 集成实战
6.1 Vue 2.x 组件封装(Options API)
<template> <div ref="lottieContainer" :style="containerStyle"></div> </template> <script> import lottie from 'lottie-web' export default { name: 'LottieAnimation', props: { // 动画数据源 animationData: { type: Object, default: null }, path: { type: String, default: '' }, // 渲染方式 renderer: { type: String, default: 'svg', validator: value => ['svg', 'canvas', 'html'].includes(value) }, // 是否循环 loop: { type: [Boolean, Number], default: true }, // 是否自动播放 autoplay: { type: Boolean, default: true }, // 播放速度 speed: { type: Number, default: 1 }, // 容器样式 width: { type: [String, Number], default: '100%' }, height: { type: [String, Number], default: '100%' } }, data() { return { animation: null } }, computed: { containerStyle() { return { width: typeof this.width === 'number' ? `${this.width}px` : this.width, height: typeof this.height === 'number' ? `${this.height}px` : this.height } } }, mounted() { this.initAnimation() }, beforeDestroy() { this.destroyAnimation() }, watch: { speed(newSpeed) { if (this.animation) { this.animation.setSpeed(newSpeed) } }, loop(newLoop) { if (this.animation) { this.animation.setLooping(newLoop) } } }, methods: { initAnimation() { if (!this.$refs.lottieContainer) { return } const config = { container: this.$refs.lottieContainer, renderer: this.renderer, loop: this.loop, autoplay: this.autoplay, rendererSettings: { preserveAspectRatio: 'xMidYMid meet', progressiveLoad: false } } // 优先使用 animationData,其次使用 path if (this.animationData) { config.animationData = this.animationData } else if (this.path) { config.path = this.path } else { console.warn('LottieAnimation: 请提供 animationData 或 path') return } this.animation = lottie.loadAnimation(config) // 设置播放速度 if (this.speed !== 1) { this.animation.setSpeed(this.speed) } // 监听事件 this.animation.addEventListener('complete', this.onComplete) this.animation.addEventListener('loopComplete', this.onLoopComplete) this.animation.addEventListener('enterFrame', this.onEnterFrame) }, destroyAnimation() { if (this.animation) { this.animation.removeEventListener('complete', this.onComplete) this.animation.removeEventListener('loopComplete', this.onLoopComplete) this.animation.removeEventListener('enterFrame', this.onEnterFrame) this.animation.destroy() this.animation = null } }, // 播放 play() { if (this.animation) { this.animation.play() } }, // 暂停 pause() { if (this.animation) { this.animation.pause() } }, // 停止 stop() { if (this.animation) { this.animation.stop() } }, // 跳转到指定帧 goToAndStop(frame, isFrame) { if (this.animation) { this.animation.goToAndStop(frame, isFrame) } }, // 跳转到指定帧并播放 goToAndPlay(frame, isFrame) { if (this.animation) { this.animation.goToAndPlay(frame, isFrame) } }, // 事件回调 onComplete() { this.$emit('complete') }, onLoopComplete() { this.$emit('loopComplete') }, onEnterFrame() { this.$emit('enterFrame', { currentFrame: this.animation.currentFrame, totalFrames: this.animation.totalFrames }) } } } </script> <style lang="scss" scoped> // 样式可以根据需要添加 </style> 6.2 Vue 2.x 使用示例
<template> <div> <h1>Lottie 动画示例</h1> <!-- 基础使用 --> <LottieAnimation :path="animationPath" :loop="true" :autoplay="true" @complete="onAnimationComplete" /> <!-- 使用 animationData --> <LottieAnimation :animation-data="animationData" :loop="false" :autoplay="false" ref="lottieRef" /> <!-- 控制按钮 --> <div> <button @click="play">播放</button> <button @click="pause">暂停</button> <button @click="stop">停止</button> <button @click="setSpeed(2)">2倍速</button> </div> </div> </template> <script> import LottieAnimation from '@/components/LottieAnimation.vue' import animationData from '@/assets/animations/loading.json' export default { name: 'AnimationDemo', components: { LottieAnimation }, data() { return { animationPath: 'https://assets5.lottiefiles.com/packages/lf20_V9t630.json', animationData: animationData } }, methods: { play() { this.$refs.lottieRef.play() }, pause() { this.$refs.lottieRef.pause() }, stop() { this.$refs.lottieRef.stop() }, setSpeed(speed) { this.$refs.lottieRef.animation.setSpeed(speed) }, onAnimationComplete() { console.log('动画播放完成') } } } </script> 6.3 Vue 3 Composition API 封装
<template> <div ref="lottieContainerRef" :style="containerStyle"></div> </template> <script setup> import { ref, computed, onMounted, onBeforeUnmount, watch } from 'vue' import lottie from 'lottie-web' const props = defineProps({ animationData: { type: Object, default: null }, path: { type: String, default: '' }, renderer: { type: String, default: 'svg', validator: value => ['svg', 'canvas', 'html'].includes(value) }, loop: { type: [Boolean, Number], default: true }, autoplay: { type: Boolean, default: true }, speed: { type: Number, default: 1 }, width: { type: [String, Number], default: '100%' }, height: { type: [String, Number], default: '100%' } }) const emit = defineEmits(['complete', 'loopComplete', 'enterFrame']) const lottieContainerRef = ref(null) const animation = ref(null) const containerStyle = computed(() => ({ width: typeof props.width === 'number' ? `${props.width}px` : props.width, height: typeof props.height === 'number' ? `${props.height}px` : props.height })) const initAnimation = () => { if (!lottieContainerRef.value) { return } const config = { container: lottieContainerRef.value, renderer: props.renderer, loop: props.loop, autoplay: props.autoplay, rendererSettings: { preserveAspectRatio: 'xMidYMid meet', progressiveLoad: false } } if (props.animationData) { config.animationData = props.animationData } else if (props.path) { config.path = props.path } else { console.warn('LottieAnimation: 请提供 animationData 或 path') return } animation.value = lottie.loadAnimation(config) if (props.speed !== 1) { animation.value.setSpeed(props.speed) } animation.value.addEventListener('complete', () => emit('complete')) animation.value.addEventListener('loopComplete', () => emit('loopComplete')) animation.value.addEventListener('enterFrame', () => { emit('enterFrame', { currentFrame: animation.value.currentFrame, totalFrames: animation.value.totalFrames }) }) } const destroyAnimation = () => { if (animation.value) { animation.value.destroy() animation.value = null } } const play = () => { if (animation.value) { animation.value.play() } } const pause = () => { if (animation.value) { animation.value.pause() } } const stop = () => { if (animation.value) { animation.value.stop() } } const goToAndStop = (frame, isFrame) => { if (animation.value) { animation.value.goToAndStop(frame, isFrame) } } const goToAndPlay = (frame, isFrame) => { if (animation.value) { animation.value.goToAndPlay(frame, isFrame) } } // 暴露方法供父组件调用 defineExpose({ play, pause, stop, goToAndStop, goToAndPlay, animation }) watch(() => props.speed, (newSpeed) => { if (animation.value) { animation.value.setSpeed(newSpeed) } }) watch(() => props.loop, (newLoop) => { if (animation.value) { animation.value.setLooping(newLoop) } }) onMounted(() => { initAnimation() }) onBeforeUnmount(() => { destroyAnimation() }) </script> 6.4 移动端 H5 集成示例(Vant + Vue 2)
<template> <div> <!-- 加载动画 --> <LottieAnimation v-if="loading" :animation-data="loadingAnimationData" :loop="true" :autoplay="true" /> <!-- 成功动画 --> <LottieAnimation v-if="showSuccess" :animation-data="successAnimationData" :loop="false" :autoplay="true" @complete="onSuccessComplete" /> </div> </template> <script> import LottieAnimation from '@/components/LottieAnimation.vue' import loadingAnimationData from '@/assets/animations/loading.json' import successAnimationData from '@/assets/animations/success.json' export default { name: 'LoadingPage', components: { LottieAnimation }, data() { return { loading: true, showSuccess: false, loadingAnimationData, successAnimationData } }, async created() { // 模拟数据加载 await this.loadData() this.loading = false this.showSuccess = true }, methods: { async loadData() { return new Promise(resolve => { setTimeout(() => { resolve() }, 2000) }) }, onSuccessComplete() { // 动画完成后跳转 setTimeout(() => { this.$router.push('/home') }, 500) } } } </script> <style lang="scss" scoped> .loading-page { display: flex; flex-direction: column; align-items: center; justify-content: center; min-height: 100vh; background: #f5f5f5; .loading-animation, .success-animation { margin-bottom: 20px; } } </style> 七、高级特性
7.1 动态替换动画
// 获取新的动画数据const newAnimationData =awaitfetch('/api/new-animation.json').then(r=> r.json())// 更新动画 animation.updateDocumentData(newAnimationData)7.2 片段播放
// 只播放动画的某个片段const animation = lottie.loadAnimation({ container: document.getElementById('container'), renderer:'svg', path:'./animation.json', initialSegment:[10,50]// 从第10帧播放到第50帧})// 动态设置片段 animation.playSegments([10,50],true)// (片段数组, 是否强制)7.3 响应式调整
// 监听窗口大小变化 window.addEventListener('resize',()=>{ animation.resize()})// 或者使用防抖优化import{ debounce }from'lodash'const handleResize =debounce(()=>{ animation.resize()},300) window.addEventListener('resize', handleResize)7.4 性能优化渲染设置
const animation = lottie.loadAnimation({ container: document.getElementById('container'), renderer:'svg', path:'./animation.json', rendererSettings:{ preserveAspectRatio:'xMidYMid slice',// 填充整个容器 clearCanvas:true,// 清除画布,避免残留 progressiveLoad:true,// 渐进式加载,提升首屏性能 hideOnTransparent:true,// 隐藏透明区域 className:'lottie-animation',// 添加类名便于样式控制 viewBoxOnly:false,// 是否只渲染 viewBox 区域 viewBoxSize:false,// 是否使用 viewBox 尺寸 context:null,// Canvas 上下文(Canvas 渲染时) scaleMode:'noScale',// 缩放模式 imagePreserveAspectRatio:'xMidYMid slice'// 图片保持宽高比}})7.5 预加载动画
// 预加载动画数据constpreloadAnimation=async(path)=>{const response =awaitfetch(path)const animationData =await response.json()return animationData }// 使用预加载的数据const animationData =awaitpreloadAnimation('./animation.json')const animation = lottie.loadAnimation({ container: document.getElementById('container'), renderer:'svg', animationData: animationData,// 使用预加载的数据 autoplay:false})// 需要时再播放 animation.play()八、性能优化
8.1 选择合适的渲染器
// 简单动画:使用 SVG(推荐) renderer:'svg'// 复杂动画或大量动画:使用 Canvas renderer:'canvas'// 避免使用 HTML 渲染器(性能较差)8.2 按需加载
// 使用动态导入constloadLottie=async()=>{const lottie =awaitimport('lottie-web')return lottie.default }// 需要时再加载const lottie =awaitloadLottie()const animation = lottie.loadAnimation({...})8.3 使用轻量版本
// 如果不需要某些功能,可以使用轻量版本import lottie from'lottie-web/build/player/lottie_light'8.4 优化动画文件
- ✅ 减少不必要的关键帧
- ✅ 简化路径和形状
- ✅ 压缩 JSON 文件(使用工具如
json-minify) - ✅ 移除未使用的资源
8.5 控制动画数量
// 限制同时播放的动画数量constMAX_ANIMATIONS=5let activeAnimations =[]constplayAnimation=(config)=>{if(activeAnimations.length >=MAX_ANIMATIONS){// 停止最旧的动画const oldest = activeAnimations.shift() oldest.destroy()}const animation = lottie.loadAnimation(config) activeAnimations.push(animation) animation.addEventListener('complete',()=>{const index = activeAnimations.indexOf(animation)if(index >-1){ activeAnimations.splice(index,1)}})}8.6 使用 Intersection Observer 懒加载
// 只在动画进入视口时加载const observer =newIntersectionObserver((entries)=>{ entries.forEach(entry=>{if(entry.isIntersecting){const animation = lottie.loadAnimation({ container: entry.target, renderer:'svg', path:'./animation.json', autoplay:true}) observer.unobserve(entry.target)}})}) document.querySelectorAll('.lottie-container').forEach(el=>{ observer.observe(el)})九、常见问题与解决方案
9.1 动画不显示
问题:动画容器存在,但动画不显示
解决方案:
// 1. 检查容器是否有尺寸.container { width:300px; height:300px;// 必须有高度}// 2. 检查 JSON 文件路径是否正确// 3. 检查 JSON 文件格式是否正确// 4. 添加错误监听 animation.addEventListener('data_failed',()=>{ console.error('动画加载失败')})9.2 动画模糊
问题:动画在高分辨率屏幕上显示模糊
解决方案:
// 使用 SVG 渲染器(矢量图形,不会模糊) renderer:'svg'// 或者提高 Canvas 分辨率 rendererSettings:{ context: canvas.getContext('2d',{ willReadFrequently:true}), clearCanvas:true}// 设置合适的容器尺寸const scale = window.devicePixelRatio ||1 canvas.width = width * scale canvas.height = height * scale 9.3 动画播放不流畅
问题:动画卡顿,帧率低
解决方案:
// 1. 使用 Canvas 渲染器 renderer:'canvas'// 2. 减少同时播放的动画数量// 3. 优化动画文件(减少关键帧)// 4. 使用 requestAnimationFrame 优化// 5. 检查是否有其他性能瓶颈(如大量 DOM 操作)9.4 跨域问题
问题:加载外部 JSON 文件时出现 CORS 错误
解决方案:
// 1. 服务器端设置 CORS 头// Access-Control-Allow-Origin: *// 2. 使用代理// 3. 将 JSON 文件放在同域下// 4. 使用 animationData 而不是 pathconst response =awaitfetch('/api/animation.json')const animationData =await response.json()const animation = lottie.loadAnimation({ container: document.getElementById('container'), renderer:'svg', animationData: animationData // 使用 animationData})9.5 内存泄漏
问题:动画播放后内存占用持续增长
解决方案:
// 组件销毁时一定要销毁动画实例beforeDestroy(){if(this.animation){this.animation.destroy()this.animation =null}}// 移除所有事件监听 animation.removeEventListener('complete', handler) animation.removeEventListener('loopComplete', handler)9.6 动画无法控制
问题:调用 play()、pause() 等方法无效
解决方案:
// 1. 确保动画已加载完成 animation.addEventListener('DOMLoaded',()=>{ animation.play()// 在加载完成后控制})// 2. 检查动画实例是否存在if(animation &&typeof animation.play ==='function'){ animation.play()}// 3. 使用 nextTick 确保 DOM 已更新(Vue)this.$nextTick(()=>{this.animation.play()})十、最佳实践
10.1 组件封装建议
<!-- 推荐的组件结构 --> <template> <div ref="container" :class="['lottie-wrapper', customClass]" :style="wrapperStyle" > <!-- 可以添加加载状态 --> <div v-if="loading"> 加载中... </div> </div> </template> <script> export default { props: { // ... props }, data() { return { loading: true, animation: null } }, mounted() { this.initAnimation() }, beforeDestroy() { this.destroyAnimation() }, methods: { async initAnimation() { try { // 加载动画数据 const animationData = await this.loadAnimationData() // 创建动画实例 this.animation = lottie.loadAnimation({ container: this.$refs.container, renderer: this.renderer, animationData: animationData, // ... 其他配置 }) // 监听加载完成 this.animation.addEventListener('DOMLoaded', () => { this.loading = false this.$emit('ready') }) // 错误处理 this.animation.addEventListener('data_failed', () => { this.loading = false this.$emit('error', new Error('动画加载失败')) }) } catch (error) { this.loading = false this.$emit('error', error) } }, async loadAnimationData() { if (this.animationData) { return this.animationData } if (this.path) { const response = await fetch(this.path) return await response.json() } throw new Error('请提供 animationData 或 path') }, destroyAnimation() { if (this.animation) { this.animation.destroy() this.animation = null } } } } </script> 10.2 错误处理
// 完善的错误处理constinitAnimation=async()=>{try{// 1. 验证容器if(!container){thrownewError('容器元素不存在')}// 2. 验证数据源if(!animationData &&!path){thrownewError('请提供 animationData 或 path')}// 3. 加载动画数据let data = animationData if(!data && path){const response =awaitfetch(path)if(!response.ok){thrownewError(`加载失败: ${response.status}`)} data =await response.json()}// 4. 创建动画const animation = lottie.loadAnimation({ container: container, renderer:'svg', animationData: data })// 5. 监听错误 animation.addEventListener('data_failed',(error)=>{ console.error('动画数据加载失败:', error)// 显示错误提示或降级方案})return animation }catch(error){ console.error('初始化动画失败:', error)// 错误处理逻辑throw error }}10.3 类型定义(TypeScript)
// lottie.d.tsdeclaremodule'lottie-web'{exportinterfaceAnimationConfig{ container: HTMLElement |string renderer:'svg'|'canvas'|'html' loop?:boolean|number autoplay?:boolean path?:string animationData?: object name?:string rendererSettings?:{ preserveAspectRatio?:string clearCanvas?:boolean progressiveLoad?:boolean hideOnTransparent?:boolean className?:string id?:string}}exportinterfaceAnimationItem{play():voidpause():voidstop():voidgoToAndStop(frame:number, isFrame?:boolean):voidgoToAndPlay(frame:number, isFrame?:boolean):voidsetSpeed(speed:number):voidsetDirection(direction:number):voidsetLooping(loop:boolean|number):voidplaySegments(segments:number[], forceFlag?:boolean):voiddestroy():voidresize():voidupdateDocumentData(path:string, value:any, flag?:boolean):voidaddEventListener(event:string,callback:()=>void):voidremoveEventListener(event:string,callback:()=>void):void totalFrames:number currentFrame:number duration:number frameRate:number isPaused:boolean loop:boolean|number}exportfunctionloadAnimation(config: AnimationConfig): AnimationItem exportfunctionpause(name?:string):voidexportfunctionplay(name?:string):voidexportfunctionsetSpeed(speed:number, name?:string):voidexportfunctionsetDirection(direction:number, name?:string):voidexportfunctionstop(name?:string):voidexportfunctiongetRegisteredAnimations(): AnimationItem[]exportfunctionfindAnimationByID(id:string): AnimationItem |null}exportdefault lottie 十一、实际应用场景
11.1 加载动画(Loading)
<template> <div v-if="loading"> <LottieAnimation :animation-data="loadingAnimationData" :loop="true" :autoplay="true" /> <p>{{ loadingText }}</p> </div> </template> <script> import LottieAnimation from '@/components/LottieAnimation.vue' import loadingAnimationData from '@/assets/animations/loading.json' export default { components: { LottieAnimation }, props: { loading: Boolean, loadingText: { type: String, default: '加载中...' } }, data() { return { loadingAnimationData } } } </script> 11.2 页面过渡动画
// 路由切换时的过渡动画 router.beforeEach((to,from, next)=>{// 显示过渡动画showTransitionAnimation()// 动画完成后跳转setTimeout(()=>{next()hideTransitionAnimation()},500)})11.3 交互动画反馈
<template> <div> <button @click="handleClick" :class="{ 'clicked': clicked }"> <LottieAnimation v-if="clicked" :animation-data="clickAnimationData" :loop="false" :autoplay="true" @complete="onClickComplete" /> <span>点击我</span> </button> </div> </template> <script> export default { data() { return { clicked: false, clickAnimationData: null } }, methods: { handleClick() { this.clicked = true // 执行点击逻辑 }, onClickComplete() { this.clicked = false } } } </script> 11.4 数据可视化动画
// 数字增长动画配合 LottieconstanimateNumber=(targetValue, duration =2000)=>{const startValue =0const startTime = Date.now()constupdate=()=>{const elapsed = Date.now()- startTime const progress = Math.min(elapsed / duration,1)const currentValue = Math.floor(startValue +(targetValue - startValue)* progress)// 更新数字显示 numberElement.textContent = currentValue // 播放对应的 Lottie 动画if(progress <1){requestAnimationFrame(update)}else{// 播放完成动画 completionAnimation.play()}}update()}11.5 空状态动画
<template> <div> <LottieAnimation :animation-data="emptyAnimationData" :loop="true" :autoplay="true" /> <p>{{ emptyText }}</p> <button @click="handleAction">{{ actionText }}</button> </div> </template> 十二、总结
12.1 核心要点
- 选择合适的渲染器:简单动画用 SVG,复杂动画用 Canvas
- 注意性能优化:控制动画数量,使用懒加载,及时销毁实例
- 完善的错误处理:监听错误事件,提供降级方案
- 组件化封装:封装成可复用的组件,提高开发效率
- 资源优化:压缩 JSON 文件,优化动画设计
12.2 学习资源
- 官方文档:https://github.com/airbnb/lottie-web
- Lottie 文件库:https://lottiefiles.com/
- After Effects 插件:Bodymovin(导出 Lottie JSON)
12.3 相关工具
- Lottie Editor:在线编辑 Lottie 动画
- Lottie Preview:预览 Lottie 文件
- JSON Minify:压缩 JSON 文件
附录:完整示例代码
A. 完整的 Vue 2 组件
<template> <div ref="lottieContainer" :class="['lottie-animation-wrapper', customClass]" :style="containerStyle" ></div> </template> <script> import lottie from 'lottie-web' export default { name: 'LottieAnimation', props: { animationData: Object, path: String, renderer: { type: String, default: 'svg', validator: v => ['svg', 'canvas', 'html'].includes(v) }, loop: { type: [Boolean, Number], default: true }, autoplay: { type: Boolean, default: true }, speed: { type: Number, default: 1 }, width: { type: [String, Number], default: '100%' }, height: { type: [String, Number], default: '100%' }, customClass: String }, data() { return { animation: null, loading: true } }, computed: { containerStyle() { return { width: typeof this.width === 'number' ? `${this.width}px` : this.width, height: typeof this.height === 'number' ? `${this.height}px` : this.height } } }, mounted() { this.initAnimation() }, beforeDestroy() { this.destroyAnimation() }, watch: { speed(newSpeed) { if (this.animation) { this.animation.setSpeed(newSpeed) } }, loop(newLoop) { if (this.animation) { this.animation.setLooping(newLoop) } } }, methods: { initAnimation() { if (!this.$refs.lottieContainer) return const config = { container: this.$refs.lottieContainer, renderer: this.renderer, loop: this.loop, autoplay: this.autoplay, rendererSettings: { preserveAspectRatio: 'xMidYMid meet', progressiveLoad: false, clearCanvas: true, hideOnTransparent: true } } if (this.animationData) { config.animationData = this.animationData } else if (this.path) { config.path = this.path } else { console.warn('LottieAnimation: 请提供 animationData 或 path') return } try { this.animation = lottie.loadAnimation(config) if (this.speed !== 1) { this.animation.setSpeed(this.speed) } this.animation.addEventListener('DOMLoaded', () => { this.loading = false this.$emit('ready') }) this.animation.addEventListener('complete', () => { this.$emit('complete') }) this.animation.addEventListener('loopComplete', () => { this.$emit('loopComplete') }) this.animation.addEventListener('enterFrame', () => { this.$emit('enterFrame', { currentFrame: this.animation.currentFrame, totalFrames: this.animation.totalFrames }) }) this.animation.addEventListener('data_failed', () => { this.loading = false this.$emit('error', new Error('动画加载失败')) }) } catch (error) { this.loading = false this.$emit('error', error) } }, destroyAnimation() { if (this.animation) { this.animation.destroy() this.animation = null } }, play() { if (this.animation) { this.animation.play() } }, pause() { if (this.animation) { this.animation.pause() } }, stop() { if (this.animation) { this.animation.stop() } }, goToAndStop(frame, isFrame = true) { if (this.animation) { this.animation.goToAndStop(frame, isFrame) } }, goToAndPlay(frame, isFrame = true) { if (this.animation) { this.animation.goToAndPlay(frame, isFrame) } }, setSpeed(speed) { if (this.animation) { this.animation.setSpeed(speed) } }, setDirection(direction) { if (this.animation) { this.animation.setDirection(direction) } }, playSegments(segments, forceFlag = false) { if (this.animation) { this.animation.playSegments(segments, forceFlag) } } } } </script> <style lang="scss" scoped> .lottie-animation-wrapper { display: inline-block; line-height: 0; } </style> 💡 提示:本文档涵盖了 Lottie-Web 的完整使用指南,从基础到高级,从理论到实践。建议按照顺序阅读,并根据实际项目需求选择合适的方案。