跳到主要内容Lottie-Web 完整技术指南 | 极客日志JavaScript大前端
Lottie-Web 完整技术指南
Lottie-Web 动画库的使用指南。内容包括 Lottie 简介、核心优势及工作原理。涵盖 NPM 安装、CDN 引入及 ES6 模块用法。深入讲解基础使用、三种渲染方式(SVG/Canvas/HTML)对比及 API 详解,包括播放控制、事件监听等。提供 Vue 2.x 和 Vue 3 Composition API 的组件封装实战代码。讨论高级特性如动态替换、片段播放、性能优化策略及 Intersection Observer 懒加载。最后总结常见问题解决方案、最佳实践及 TypeScript 类型定义,帮助开发者高效实现高质量矢量动画。
城市逃兵31 浏览 一、什么是 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 安装(推荐)
npm install lottie-web
3.2 CDN 引入
<script src=>
"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 项目引入
import lottie from 'lottie-web'
Vue.prototype.$lottie = lottie
四、基础使用
4.1 最简单的示例
<!DOCTYPE html>
<html>
<head><title>Lottie 基础示例</title></head>
<body>
<div id="lottie-container" style="width: 300px;height: 300px;"></div>
<script src="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',
loop: true,
autoplay: true,
path: 'https://assets5.lottiefiles.com/packages/lf20_V9t630.json'
})
</script>
</body>
</html>
4.2 使用本地 JSON 文件
const animation = lottie.loadAnimation({
container: document.getElementById('lottie-container'),
renderer: 'svg',
loop: true,
autoplay: true,
path: './animations/my-animation.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
})
4.3 三种渲染方式对比
SVG 渲染(推荐)
const animation = lottie.loadAnimation({
container: document.getElementById('lottie-container'),
renderer: 'svg',
loop: true,
autoplay: true,
path: './animation.json'
})
- 矢量图形,任意缩放不失真
- 支持 CSS 样式控制
- 文件体积相对较小
- 适合简单到中等复杂度的动画
- 复杂动画性能可能不如 Canvas
- DOM 元素较多时可能影响性能
Canvas 渲染
const animation = lottie.loadAnimation({
container: document.getElementById('lottie-container'),
renderer: 'canvas',
loop: true,
autoplay: true,
path: './animation.json'
})
- 性能好,适合复杂动画
- 不会产生大量 DOM 元素
- 适合大量动画同时播放
- 缩放可能失真(取决于分辨率设置)
- 不支持 CSS 样式控制
HTML 渲染
const animation = lottie.loadAnimation({
container: document.getElementById('lottie-container'),
renderer: 'html',
loop: true,
autoplay: true,
path: './animation.json'
})
- 可以使用 CSS 完全控制样式
- 支持文本选择等 HTML 特性
- 性能较差
- 兼容性不如 SVG 和 Canvas
- 不推荐用于生产环境
五、API 详解
5.1 loadAnimation() 配置选项
const animation = lottie.loadAnimation({
container: element,
renderer: 'svg',
path: 'path/to/animation.json',
animationData: {},
loop: true,
autoplay: true,
rendererSettings: {
preserveAspectRatio: 'xMidYMid meet',
clearCanvas: true,
context: null,
progressiveLoad: false,
hideOnTransparent: true,
className: '',
id: ''
},
name: 'myAnimation',
initialSegment: [0, 30],
})
5.2 动画实例方法
播放控制
animation.play()
animation.pause()
animation.stop()
animation.goToAndStop(50, true)
animation.goToAndPlay(50, true)
animation.setSpeed(2)
animation.setDirection(1)
animation.setLooping(true)
属性获取
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 = await fetch('/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]
})
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,
viewBoxSize: false,
context: null,
scaleMode: 'noScale',
imagePreserveAspectRatio: 'xMidYMid slice'
}
})
7.5 预加载动画
const preloadAnimation = async (path) => {
const response = await fetch(path)
const animationData = await response.json()
return animationData
}
const animationData = await preloadAnimation('./animation.json')
const animation = lottie.loadAnimation({
container: document.getElementById('container'),
renderer: 'svg',
animationData: animationData,
autoplay: false
})
animation.play()
八、性能优化
8.1 选择合适的渲染器
renderer: 'svg'
renderer: 'canvas'
8.2 按需加载
const loadLottie = async () => {
const lottie = await import('lottie-web')
return lottie.default
}
const lottie = await loadLottie()
const animation = lottie.loadAnimation({...})
8.3 使用轻量版本
import lottie from 'lottie-web/build/player/lottie_light'
8.4 优化动画文件
- 减少不必要的关键帧
- 简化路径和形状
- 压缩 JSON 文件(使用工具如
json-minify)
- 移除未使用的资源
8.5 控制动画数量
const MAX_ANIMATIONS = 5
let activeAnimations = []
const playAnimation = (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 = new IntersectionObserver((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 动画不显示
.container {
width: 300px;
height: 300px;
}
animation.addEventListener('data_failed', () => {
console.error('动画加载失败')
})
9.2 动画模糊
renderer: 'svg'
rendererSettings: {
context: canvas.getContext('2d', { willReadFrequently: true }),
clearCanvas: true
}
const scale = window.devicePixelRatio || 1
canvas.width = width * scale
canvas.height = height * scale
9.3 动画播放不流畅
9.4 跨域问题
问题:加载外部 JSON 文件时出现 CORS 错误
const response = await fetch('/api/animation.json')
const animationData = await response.json()
const animation = lottie.loadAnimation({
container: document.getElementById('container'),
renderer: 'svg',
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() 等方法无效
animation.addEventListener('DOMLoaded', () => {
animation.play()
})
if (animation && typeof animation.play === 'function') {
animation.play()
}
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 错误处理
const initAnimation = async () => {
try {
if (!container) {
throw new Error('容器元素不存在')
}
if (!animationData && !path) {
throw new Error('请提供 animationData 或 path')
}
let data = animationData
if (!data && path) {
const response = await fetch(path)
if (!response.ok) {
throw new Error(`加载失败:${response.status}`)
}
data = await response.json()
}
const animation = lottie.loadAnimation({
container: container,
renderer: 'svg',
animationData: data
})
animation.addEventListener('data_failed', (error) => {
console.error('动画数据加载失败:', error)
})
return animation
} catch (error) {
console.error('初始化动画失败:', error)
throw error
}
}
10.3 类型定义(TypeScript)
declare module 'lottie-web' {
export interface AnimationConfig {
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
}
}
export interface AnimationItem {
play(): void
pause(): void
stop(): void
goToAndStop(frame: number, isFrame?: boolean): void
goToAndPlay(frame: number, isFrame?: boolean): void
setSpeed(speed: number): void
setDirection(direction: number): void
setLooping(loop: boolean | number): void
playSegments(segments: number[], forceFlag?: boolean): void
destroy(): void
resize(): void
updateDocumentData(path: string, value: any, flag?: boolean): void
addEventListener(event: string, callback: () => void): void
removeEventListener(event: string, callback: () => void): void
totalFrames: number
currentFrame: number
duration: number
frameRate: number
isPaused: boolean
loop: boolean | number
}
export function loadAnimation(config: AnimationConfig): AnimationItem
export function pause(name?: string): void
export function play(name?: string): void
export function setSpeed(speed: number, name?: string): void
export function setDirection(direction: number, name?: string): void
export function stop(name?: string): void
export function getRegisteredAnimations(): AnimationItem[]
export function findAnimationByID(id: string): AnimationItem | null
}
export default 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 数据可视化动画
const animateNumber = (targetValue, duration = 2000) => {
const startValue = 0
const startTime = Date.now()
const update = () => {
const elapsed = Date.now() - startTime
const progress = Math.min(elapsed / duration, 1)
const currentValue = Math.floor(startValue + (targetValue - startValue) * progress)
numberElement.textContent = currentValue
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 学习资源
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 的完整使用指南,从基础到高级,从理论到实践。建议按照顺序阅读,并根据实际项目需求选择合适的方案。
相关免费在线工具
- Keycode 信息
查找任何按下的键的javascript键代码、代码、位置和修饰符。 在线工具,Keycode 信息在线工具,online
- Escape 与 Native 编解码
JavaScript 字符串转义/反转义;Java 风格 \uXXXX(Native2Ascii)编码与解码。 在线工具,Escape 与 Native 编解码在线工具,online
- JavaScript / HTML 格式化
使用 Prettier 在浏览器内格式化 JavaScript 或 HTML 片段。 在线工具,JavaScript / HTML 格式化在线工具,online
- JavaScript 压缩与混淆
Terser 压缩、变量名混淆,或 javascript-obfuscator 高强度混淆(体积会增大)。 在线工具,JavaScript 压缩与混淆在线工具,online
- Base64 字符串编码/解码
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
- Base64 文件转换器
将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online