Lottie-Web 完整技术指南:让动画开发更简单高效

📚 目录


一、什么是 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 核心要点

  1. 选择合适的渲染器:简单动画用 SVG,复杂动画用 Canvas
  2. 注意性能优化:控制动画数量,使用懒加载,及时销毁实例
  3. 完善的错误处理:监听错误事件,提供降级方案
  4. 组件化封装:封装成可复用的组件,提高开发效率
  5. 资源优化:压缩 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 的完整使用指南,从基础到高级,从理论到实践。建议按照顺序阅读,并根据实际项目需求选择合适的方案。

Read more

2024第十五届蓝桥杯大赛软件赛省赛C/C++ 大学 B 组

2024第十五届蓝桥杯大赛软件赛省赛C/C++ 大学 B 组

记录刷题的过程、感悟、题解。 希望能帮到,那些与我一同前行的,来自远方的朋友😉 大纲:  1、握手问题-(解析)-简单组合问题(别人叫她 鸽巢定理)😇,感觉叫高级了  2、小球反弹-(解析)-简单物理问题,不太容易想  3、好数-(解析)-简单运用分支计算  4、R 格式-(解析)-高精度,不是快速幂😉  5、宝石组合-(解析)-lcm推论(gcd、lcm结合)  6、数字接龙-(解析)-DFS(蓝桥专属、每年必有一道)  7、拔河-(解析)-定一端,动一端😎 题目: 1、握手问题 问题描述

By Ne0inhk
Elasticsearch从入门到实践:核心概念到Kibana测试与C++客户端封装

Elasticsearch从入门到实践:核心概念到Kibana测试与C++客户端封装

文章目录 * 概念简述 * 安装与配置 * 测试示例 * 客户端API使用 * 二次封装源码 概念简述 Elasticsearch,简称 ES,它是个开源分布式搜索引擎,它的特点有:分布式、零配置、自动发现、索引自动分片、索引副本机制、restful 风格接口、多数据源、自动搜索负载等。ES类似数据库,相比数据库,它在搜索功能上更为实用、高效。 在搜索上与数据库的区别? 数据库的搜索策略类似二叉搜索树,但在文本搜索场景下,只能使用like模糊匹配,效率较低。而es主要做分词搜索,比如“你好,世界”,会被分成:“你”、“好”、“世”、“界”、“你好”、“世界”… es核心概念 * 索引:一个索引就是一个拥有几分相似特征的文档的集合,类似于mysql数据库中的库。 * 类型:一个类型是索引的一个逻辑上的分类/分区,类似于mysql数据库中库结构下的表。 * 字段:

By Ne0inhk

C++ 多线程与并发系统取向(六)—— 任务系统骨架:mutex + condition_variable + atomic 组合实战

一、目标:做一个“可用”的最小任务系统 我们要实现一个简化版任务系统,支持: * ✅ 提交任务(任意函数) * ✅ 工作线程自动执行 * ✅ 无任务时阻塞等待 * ✅ 支持安全关闭 * ✅ 正确释放线程 你可以理解为: 线程池的前身。 二、设计思路(系统取向) 我们需要三类能力: 1️⃣ 资源保护(队列) * 任务队列 * 多线程访问 * 必须用 mutex 保护 2️⃣ 线程协作(等待任务) * 队列空 → 线程休眠 * 新任务 → 唤醒线程 * 使用 condition_variable 3️⃣ 状态控制(停止系统) * 停止标志 * 使用 atomic<bool> 三、整体结构图 主线程 │ ├── submit(

By Ne0inhk
csp信奥赛c++高频考点假期集训(分模块进阶)

csp信奥赛c++高频考点假期集训(分模块进阶)

csp信奥赛c++高频考点假期集训(分模块进阶) 课程简介: csp信奥赛C++高频考点集训课涵盖12大高频考点专题: 1、语法基础专题 2、数学思维专题 3、模拟算法专题 4、排序算法专题 5、贪心算法专题 6、二分算法专题 7、前缀和&差分 8、深度优先搜索 9、广度优先搜索 10、动态规划专题 11、栈和队列专题 12、树和图专题 csp信奥赛C++高频考点集训:视频简介 适合人群: 正在学习csp信奥赛的学生 准备参加csp信奥赛的学生 想通过csp信奥赛集训课提升解题方法和技巧的学生 想通过csp信奥赛集训课提升自身整体编码能力的学生 你将会学到: csp信奥赛C++12大高频考点专题,助力你冲刺csp一等奖 课程直通链接: https://edu.ZEEKLOG.net/course/

By Ne0inhk