什么是 Lottie
Lottie 是一个由 Airbnb 开发的库,它可以在 Web、iOS、Android 和 React Native 上渲染 Adobe After Effects 动画。简单来说,设计师可以在 After Effects 中创建精美的动画,然后通过 Bodymovin 插件导出为 JSON 格式,开发者再通过 Lottie 库将这些 JSON 文件渲染成高质量的动画。
这意味着你可以在你的网站或应用中使用复杂的动画,而不需要使用 GIF 或视频,从而获得更好的性能和质量。
为什么使用 Lottie
在传统的 Web 开发中,如果我们想要在网页中展示动画,通常有以下几种方式:
- GIF 图片:文件较大,质量较低,无法交互
- CSS 动画:适合简单动画,复杂动画难以实现
- JavaScript 动画库:如 GSAP,需要手动编写动画代码
- 视频:文件大,加载慢,不够灵活
而 Lottie 提供了一种全新的方式,它具有以下优势:
- 文件小:JSON 格式的动画文件通常比 GIF 或视频小得多
- 无损缩放:基于矢量的动画可以无限缩放而不失真
- 可交互:可以控制动画的播放、暂停、速度等
- 性能好:使用 SVG 或 Canvas 渲染,性能优异
- 设计开发协作:设计师可以直接输出动画,开发者直接使用
基础知识准备
INFO
在开始使用 Lottie 之前,你需要了解一些基础知识:
1. 安装 lottie-web
首先,我们需要安装 lottie-web 库:
# 使用 npm
npm install lottie-web
# 使用 yarn
yarn add lottie-web
# 使用 pnpm
pnpm add lottie-web
# 使用 bun
bun add lottie-web
2. 获取 Lottie 动画文件
你可以从以下途径获取 Lottie 动画文件(JSON 格式):
- 设计师使用
After Effects
和Bodymovin
插件导出 - 使用在线资源,如 LottieFiles
3. 了解 Vue3 的组合式 API
本教程将使用 Vue3
的组合式 API (Composition API) 来封装 Lottie
组件,如果你还不熟悉,可以先了解以下概念:
ref
和reactive
:用于创建响应式数据computed
:计算属性watch
:侦听器onMounted
和onUnmounted
:生命周期钩子defineProps
和defineEmits
:组件属性和事件
最简单的使用方法
提示
在深入封装之前,让我们先看看最基础的使用方法,这有助于理解 Lottie 的工作原理。
1. 创建一个简单的 HTML 结构
<div id="lottie-container" style="width: 300px; height: 300px;"></div>
2. 使用 JS 加载动画
import lottie from 'lottie-web';
// 假设我们已经导入了动画数据
import animationData from './path/to/animation.json';
// 在组件挂载后
onMounted(() => {
const animation = lottie.loadAnimation({
container: document.getElementById('lottie-container'), // 动画容器
renderer: 'svg', // 渲染器类型:'svg' | 'canvas' | 'html'
loop: true, // 是否循环播放
autoplay: true, // 是否自动播放
animationData: animationData // 动画数据
});
// 可以通过 animation 对象控制动画
// animation.play(); // 播放
// animation.pause(); // 暂停
// animation.stop(); // 停止
});
3. 在 Vue3 中的简单使用
在 Vue3 中,我们可以结合 ref
来引用 DOM 元素:
<template>
<div ref="lottieContainer" style="width: 300px; height: 300px;"></div>
</template>
<script setup>
import lottie from 'lottie-web';
import { ref, onMounted, onUnmounted } from 'vue';
import animationData from './path/to/animation.json';
const lottieContainer = ref(null);
let animation = null;
onMounted(() => {
if (lottieContainer.value) {
animation = lottie.loadAnimation({
container: lottieContainer.value,
renderer: 'svg',
loop: true,
autoplay: true,
animationData: animationData
});
}
});
onUnmounted(() => {
// 组件卸载时销毁动画实例,避免内存泄漏
if (animation) {
animation.destroy();
}
});
</script>
NOTE
这是最基础的使用方法,但在实际项目中,我们通常需要更灵活的控制和更好的封装。接下来,我们将一步步学习如何封装一个功能完善的 Lottie 组件。
基础封装
提示
现在我们已经了解了 Lottie 的基本使用方法,接下来让我们开始封装一个简单的 Lottie 组件。封装的目的是让组件更易于复用,并且提供更好的接口供父组件调用。
1. 创建基础组件
首先,让我们创建一个名为 LottieAnimation.vue
的基础组件:
<template>
<div class="lottie-container" ref="container"></div>
</template>
<script setup>
import lottie from 'lottie-web';
import { ref, onMounted, onUnmounted } from 'vue';
// 定义组件属性
const props = defineProps({
animationData: {
type: Object,
required: true
}
});
const container = ref(null);
let animation = null;
onMounted(() => {
if (container.value && props.animationData) {
animation = lottie.loadAnimation({
container: container.value,
renderer: 'svg',
loop: true,
autoplay: true,
animationData: props.animationData
});
}
});
onUnmounted(() => {
if (animation) {
animation.destroy();
}
});
</script>
<style scoped>
.lottie-container {
width: 100%;
height: 100%;
}
</style>
这个组件非常简单,它只接收一个必需的
animationData
属性,并在组件挂载时创建动画,在组件卸载时销毁动画。
2. 使用基础组件
现在我们可以在其他组件中使用这个基础 Lottie 组件:
<template>
<div style="width: 200px; height: 200px;">
<LottieAnimation :animation-data="animationData" />
</div>
</template>
<script setup>
import LottieAnimation from './LottieAnimation.vue';
import animationData from './path/to/animation.json';
</script>
不做其他配置,默认为
自动播放
以及循环播放
的SVG动画:在这里插入图片描述
3. 添加基本配置选项
我们的基础组件还比较简单,让我们添加一些常用的配置选项,如循环播放、自动播放等:
<template>
<div class="lottie-container" ref="container"></div>
</template>
<script setup>
import lottie from 'lottie-web';
import { ref, onMounted, onUnmounted } from 'vue';
// 定义组件属性
const props = defineProps({
animationData: {
type: Object,
required: true
},
loop: { // 新增loop控制是否循环播放
type: Boolean,
default: true
},
autoplay: { // 新增autoplay控制是否自动播放//
type: Boolean,
default: true
},
width: {
type: [String, Number],
default: '100%'
},
height: {
type: [String, Number],
default: '100%'
}
});
const container = ref(null);
let animation = null;
onMounted(() => {
if (container.value && props.animationData) {
animation = lottie.loadAnimation({
container: container.value,
renderer: 'svg',
loop: props.loop, // 通过prop传入的loop//
autoplay: props.autoplay, // 通过prop传入的autoplay//
animationData: props.animationData
});
}
});
onUnmounted(() => {
if (animation) {
animation.destroy();
}
});
</script>
<style scoped>
.lottie-container {
width: v-bind('props.width');
height: v-bind('props.height');
}
</style>
TIP
现在我们的组件可以接受更多的配置选项,使用起来更加灵活:
// 父组件
<template>
<LottieAnimation
:animation-data="animationData"
:loop="false"
:autoplay="true"
width="300px"
height="300px"
/>
</template>
设置loop为false,动画播放完成便会停止:
在这里插入图片描述
4. 添加基本控制方法
添加基本控制方法
目前我们组件只能在初始化时设置一些配置,但无法在运行时控制动画。让我们添加一些基本的控制方法,如播放
、暂停
和停止
:
// LottieAnimation.vue
<template>
<div class="lottie-container" ref="container"></div>
</template>
<script setup>
import lottie from 'lottie-web';
import { ref, onMounted, onUnmounted } from 'vue';
// 定义组件属性
const props = defineProps({
animationData: {
type: Object,
required: true
},
loop: {
type: Boolean,
default: true
},
autoplay: {
type: Boolean,
default: true
},
width: {
type: [String, Number],
default: '100%'
},
height: {
type: [String, Number],
default: '100%'
}
});
const container = ref(null);
let animation = null;
// 定义控制方法
const play = () => {
animation?.play(); // play-----从当前帧开始播放//
};
const pause = () => {
animation?.pause(); // pause-----暂停并保持当前帧//
};
const stop = () => {
animation?.stop(); // stop-----停止播放并重置到第0帧//
};
// 暴露方法给父组件
defineExpose({
play,
pause,
stop
});
onMounted(() => {
if (container.value && props.animationData) {
animation = lottie.loadAnimation({
container: container.value,
renderer: 'svg',
loop: props.loop,
autoplay: props.autoplay,
animationData: props.animationData
});
}
});
onUnmounted(() => {
if (animation) {
animation.destroy();
}
});
</script>
<style scoped>
.lottie-container {
width: v-bind('props.width');
height: v-bind('props.height');
}
</style>
NOTE
现在父组件可以通过 ref 来调用这些方法:
// 父组件
<template>
<div>
<LottieAnimation
ref="lottieRef"
:animation-data="animationData"
:autoplay="false"
/>
<button @click="playAnimation">播放</button>
<button @click="pauseAnimation">暂停</button>
<button @click="stopAnimation">停止</button>
</div>
</template>
<script setup>
import { ref } from 'vue';
import LottieAnimation from './LottieAnimation.vue';
import animationData from './path/to/animation.json';
const lottieRef = ref(null);
const playAnimation = () => {
lottieRef.value?.play();
};
const pauseAnimation = () => {
lottieRef.value?.pause();
};
const stopAnimation = () => {
lottieRef.value?.stop();
};
</script>
设置自动播放为
false
,点击播放
按钮从当前帧开始播放动画,暂停
按钮会保持当前帧,再次点击播放
继续从当前帧开始播放,停止
按钮会终止动画重置当前帧为0
在这里插入图片描述
进阶封装
进阶封装
现在我们已经有了一个基础的 Lottie
组件,但它还缺少一些高级功能,如事件处理、动画速度控制、方向控制等。接下来,我们将逐步添加这些功能。
1. 添加事件处理
Lottie 提供了多种事件,如动画完成、循环完成等。我们可以将这些事件暴露给父组件:
// LottieAnimation.vue
<template>
<div class="lottie-container" ref="container"></div>
</template>
<script setup>
import lottie from 'lottie-web';
import { ref, onMounted, onUnmounted } from 'vue';
// 定义组件属性
const props = defineProps({
animationData: {
type: Object,
required: true
},
loop: {
type: Boolean,
default: true
},
autoplay: {
type: Boolean,
default: true
},
width: {
type: [String, Number],
default: '100%'
},
height: {
type: [String, Number],
default: '100%'
}
});
// 定义事件
const emit = defineEmits([
'complete',
'loopComplete',
'enterFrame',
'segmentStart',
'destroy'
]);
const container = ref(null);
let animation = null;
// 定义控制方法
const play = () => {
animation?.play();
};
const pause = () => {
animation?.pause();
};
const stop = () => {
animation?.stop();
};
// 暴露方法给父组件
defineExpose({
play,
pause,
stop
});
onMounted(() => {
if (container.value && props.animationData) {
animation = lottie.loadAnimation({
container: container.value,
renderer: 'svg',
loop: props.loop,
autoplay: props.autoplay,
animationData: props.animationData
});
// 注册事件监听
animation.addEventListener('complete', () => emit('complete'));
animation.addEventListener('loopComplete', () => emit('loopComplete'));
animation.addEventListener('enterFrame', (e) => emit('enterFrame', e));
animation.addEventListener('segmentStart', (e) => emit('segmentStart', e));
animation.addEventListener('destroy', () => emit('destroy'));
}
});
onUnmounted(() => {
if (animation) {
// 移除事件监听//
animation.removeEventListener('complete');
animation.removeEventListener('loopComplete');
animation.removeEventListener('enterFrame');
animation.removeEventListener('segmentStart');
animation.removeEventListener('destroy');
// 销毁动画实例//
animation.destroy();
animation = null;
}
});
</script>
<style scoped>
.lottie-container {
width: v-bind('props.width');
height: v-bind('props.height');
}
</style>
现在父组件可以监听这些事件:
// 父组件
<template>
<LottieAnimation
:animation-data="animationData"
@complete="onComplete"
@loop-complete="onLoopComplete"
/>
</template>
<script setup>
import LottieAnimation from './LottieAnimation.vue';
import animationData from './path/to/animation.json';
const onComplete = () => {
console.log('动画播放完成');
};
const onLoopComplete = () => {
console.log('动画循环播放完成');
};
</script>
事件相关文档可翻到本文底部查看 API 参考
2. 添加速度和方向控制
添加速度和方向控制
Lottie 允许我们控制动画的播放速度和方向,让我们将这些功能添加到组件中:
<template>
<div class="lottie-container" ref="container"></div>
</template>
<script setup>
import lottie from 'lottie-web';
import { ref, onMounted, onUnmounted, watch } from 'vue';
// 定义组件属性
const props = defineProps({
animationData: {
type: Object,
required: true
},
loop: {
type: Boolean,
default: true
},
autoplay: {
type: Boolean,
default: true
},
width: {
type: [String, Number],
default: '100%'
},
height: {
type: [String, Number],
default: '100%'
},
speed: { // 设置动画速度//
type: Number,
default: 1
},
direction: { // 设置动画方向//
type: Number,
default: 1,
validator: (value) => value === 1 || value === -1
}
});
// 定义事件
const emit = defineEmits([
'complete',
'loopComplete',
'enterFrame',
'segmentStart',
'destroy'
]);
const container = ref(null);
let animation = null;
// 定义控制方法
const play = () => {
animation?.play();
};
const pause = () => {
animation?.pause();
};
const stop = () => {
animation?.stop();
};
const setSpeed = (speed) => {
if (animation) {
animation.setSpeed(speed);
}
};
const setDirection = (direction) => {
if (animation) {
animation.setDirection(direction);
}
};
// 暴露方法给父组件
defineExpose({
play,
pause,
stop,
setSpeed,
setDirection
});
// 监听属性变化
watch(() => props.speed, (newSpeed) => {
if (animation) {
// 属性变化重新设置动画速度
animation.setSpeed(newSpeed);
}
});
watch(() => props.direction, (newDirection) => {
if (animation) {
// 属性变化重新设置动画方向
animation.setDirection(newDirection);
}
});
onMounted(() => {
if (container.value && props.animationData) {
animation = lottie.loadAnimation({
container: container.value,
renderer: 'svg',
loop: props.loop,
autoplay: props.autoplay,
animationData: props.animationData
});
// 设置初始速度和方向
animation.setSpeed(props.speed);
animation.setDirection(props.direction);
// 注册事件监听
animation.addEventListener('complete', () => emit('complete'));
animation.addEventListener('loopComplete', () => emit('loopComplete'));
animation.addEventListener('enterFrame', (e) => emit('enterFrame', e));
animation.addEventListener('segmentStart', (e) => emit('segmentStart', e));
animation.addEventListener('destroy', () => emit('destroy'));
}
});
onUnmounted(() => {
if (animation) {
// 移除事件监听
animation.removeEventListener('complete');
animation.removeEventListener('loopComplete');
animation.removeEventListener('enterFrame');
animation.removeEventListener('segmentStart');
animation.removeEventListener('destroy');
// 销毁动画实例
animation.destroy();
animation = null;
}
});
</script>
<style scoped>
.lottie-container {
width: v-bind('props.width');
height: v-bind('props.height');
}
</style>
NOTE
现在通过父组件可以控制动画的速度和方向:
// 父组件
<template>
<div>
<LottieAnimation
ref="lottieRef"
:animation-data="animationData"
:speed="speed"
:direction="direction"
/>
<div>
<label>速度:</label>
<input type="range" min="0.1" max="3" step="0.1" v-model="speed">
<span>{{ speed }}x</span>
</div>
<div>
<label>方向:</label>
<button @click="toggleDirection">切换方向</button>
</div>
</div>
</template>
<script setup>
import { ref } from 'vue';
import LottieAnimation from './LottieAnimation.vue';
import animationData from './path/to/animation.json';
const lottieRef = ref(null);
const speed = ref(1);
const direction = ref(1);
const toggleDirection = () => {
direction.value = direction.value === 1 ? -1 : 1;
};
</script>
3. 添加帧控制和分段播放
添加帧控制和分段播放
Lottie 还允许我们控制动画的帧和分段播放,这对于复杂动画的精确控制非常有用:
<template>
<div class="lottie-container" ref="container"></div>
</template>
<script setup>
import lottie from 'lottie-web';
import { ref, onMounted, onUnmounted, watch } from 'vue';
// 定义组件属性
const props = defineProps({
animationData: {
type: Object,
required: true
},
loop: {
type: Boolean,
default: true
},
autoplay: {
type: Boolean,
default: true
},
width: {
type: [String, Number],
default: '100%'
},
height: {
type: [String, Number],
default: '100%'
},
speed: {
type: Number,
default: 1
},
direction: {
type: Number,
default: 1,
validator: (value) => value === 1 || value === -1
},
segments: {
type: Array,
default: null
}
});
// 定义事件
const emit = defineEmits([
'complete',
'loopComplete',
'enterFrame',
'segmentStart',
'destroy'
]);
const container = ref(null);
let animation = null;
// 定义控制方法
const play = () => {
animation?.play();
};
const pause = () => {
animation?.pause();
};
const stop = () => {
animation?.stop();
};
const setSpeed = (speed) => {
if (animation) {
animation.setSpeed(speed);
}
};
const setDirection = (direction) => {
if (animation) {
animation.setDirection(direction);
}
};
const goToAndPlay = (value, isFrame = true) => {
if (animation) {
animation.goToAndPlay(value, isFrame);
}
};
const goToAndStop = (value, isFrame = true) => {
if (animation) {
animation.goToAndStop(value, isFrame);
}
};
const setSegment = (startFrame, endFrame) => {
if (animation) {
animation.setSegment(startFrame, endFrame);
}
};
const playSegments = (segments, forceFlag) => {
if (animation) {
animation.playSegments(segments, forceFlag);
}
};
// 暴露方法给父组件
defineExpose({
play,
pause,
stop,
setSpeed,
setDirection,
goToAndPlay,
goToAndStop,
setSegment,
playSegments
});
// 监听属性变化
watch(() => props.speed, (newSpeed) => {
if (animation) {
animation.setSpeed(newSpeed);
}
});
watch(() => props.direction, (newDirection) => {
if (animation) {
animation.setDirection(newDirection);
}
});
watch(() => props.segments, (newSegments) => {
if (animation && newSegments) {
animation.playSegments(newSegments, true);
}
}, { deep: true });
onMounted(() => {
if (container.value && props.animationData) {
animation = lottie.loadAnimation({
container: container.value,
renderer: 'svg',
loop: props.loop,
autoplay: props.autoplay,
animationData: props.animationData
});
// 设置初始速度和方向
animation.setSpeed(props.speed);
animation.setDirection(props.direction);
// 设置初始分段
if (props.segments) {
animation.playSegments(props.segments, true);
}
// 注册事件监听
animation.addEventListener('complete', () => emit('complete'));
animation.addEventListener('loopComplete', () => emit('loopComplete'));
animation.addEventListener('enterFrame', (e) => emit('enterFrame', e));
animation.addEventListener('segmentStart', (e) => emit('segmentStart', e));
animation.addEventListener('destroy', () => emit('destroy'));
}
});
onUnmounted(() => {
if (animation) {
// 移除事件监听
animation.removeEventListener('complete');
animation.removeEventListener('loopComplete');
animation.removeEventListener('enterFrame');
animation.removeEventListener('segmentStart');
animation.removeEventListener('destroy');
// 销毁动画实例
animation.destroy();
animation = null;
}
});
</script>
<style scoped>
.lottie-container {
width: v-bind('props.width');
height: v-bind('props.height');
}
</style>
INFO
现在父组件可以更精确地控制动画:
// 父组件
<template>
<div>
<LottieAnimation
ref="lottieRef"
:animation-data="animationData"
/>
<div>
<button @click="goToFrame(30)">跳转到第30帧开始</button>
<button @click="playFrames(60, 80)">播放60-80帧</button>
</div>
</div>
</template>
<script setup>
import { ref } from 'vue';
import LottieAnimation from './LottieAnimation.vue';
import animationData from './path/to/animation.json';
const lottieRef = ref(null);
const goToFrame = (frame) => {
lottieRef.value?.goToAndPlay(frame, true);
};
const playFrames = (start, end) => {
lottieRef.value?.playSegments([start, end], true);
};
</script>
INFO
到目前为止,我们已经封装了一个功能相对完善的 Lottie 组件,它可以满足大多数基本需求。在下一部分,我们将进一步完善这个组件,添加更多高级功能,并提供一个完整的 API 参考。
完整组件封装
完整组件封装
在前面的章节中,我们已经逐步构建了一个功能相对完善的 Lottie 组件。现在,让我们将所有功能整合起来,创建一个完整的 Lottie 组件,并添加一些额外的优化。
1. 添加渲染器选项
INFO
Lottie 支持多种渲染器,包括 SVG
、Canvas
和 HTML
。默认情况下,我们使用 SVG
渲染器,但在某些情况下,其他渲染器可能更适合。对于复杂动画,Canvas
渲染器会比 SVG
渲染器性能更好,让我们添加渲染器选项:
<template>
<div class="lottie-container" ref="container" :style="containerStyle"></div>
</template>
<script setup>
import lottie from 'lottie-web';
import { ref, onMounted, onUnmounted, watch, computed } from 'vue';
// 定义组件属性
const props = defineProps({
animationData: {
type: Object,
required: true
},
loop: {
type: Boolean,
default: true
},
autoplay: {
type: Boolean,
default: true
},
width: {
type: [String, Number],
default: '100%'
},
height: {
type: [String, Number],
default: '100%'
},
speed: {
type: Number,
default: 1
},
direction: {
type: Number,
default: 1,
validator: (value) => value === 1 || value === -1
},
segments: {
type: Array,
default: null
},
// 通过父组件传入renderer决定渲染器
renderer: {
type: String,
default: 'svg',
validator: (value) => ['svg', 'canvas', 'html'].includes(value)
},
rendererSettings: {
type: Object,
default: () => ({})
},
initialSegment: {
type: Array,
default: null
}
});
// 计算样式
const containerStyle = computed(() => {
const width = typeof props.width === 'number' ? `${props.width}px` : props.width;
const height = typeof props.height === 'number' ? `${props.height}px` : props.height;
return {
width,
height,
overflow: 'hidden'
};
});
// 定义事件
const emit = defineEmits([
'complete',
'loopComplete',
'enterFrame',
'segmentStart',
'config_ready',
'data_ready',
'data_failed',
'loaded_images',
'DOMLoaded',
'destroy',
'error'
]);
const container = ref(null);
let animation = ref(null);
// 定义控制方法
const play = () => {
animation.value?.play();
};
const pause = () => {
animation.value?.pause();
};
const stop = () => {
animation.value?.stop();
};
const setSpeed = (speed) => {
if (animation.value) {
animation.value.setSpeed(speed);
}
};
const setDirection = (direction) => {
if (animation.value) {
animation.value.setDirection(direction);
}
};
const goToAndPlay = (value, isFrame = true) => {
if (animation.value) {
animation.value.goToAndPlay(value, isFrame);
}
};
const goToAndStop = (value, isFrame = true) => {
if (animation.value) {
animation.value.goToAndStop(value, isFrame);
}
};
const setSegment = (startFrame, endFrame) => {
if (animation.value) {
animation.value.setSegment(startFrame, endFrame);
}
};
const playSegments = (segments, forceFlag) => {
if (animation.value) {
animation.value.playSegments(segments, forceFlag);
}
};
const getDuration = (inFrames = true) => {
if (animation.value) {
return animation.value.getDuration(inFrames);
}
return 0;
};
// 暴露方法给父组件
defineExpose({
play,
pause,
stop,
setSpeed,
setDirection,
goToAndPlay,
goToAndStop,
setSegment,
playSegments,
getDuration,
lottieInstance: animation
});
// 初始化 Lottie 动画
const initLottie = () => {
if (!container.value || !props.animationData) return;
animation.value = lottie.loadAnimation({
container: container.value,
renderer: props.renderer,
loop: props.loop,
autoplay: props.autoplay,
animationData: props.animationData,
rendererSettings: props.rendererSettings,
initialSegment: props.initialSegment
});
// 设置初始速度和方向
animation.value.setSpeed(props.speed);
animation.value.setDirection(props.direction);
// 设置初始分段
if (props.segments) {
animation.value.playSegments(props.segments, true);
}
// 注册事件监听
animation.value.addEventListener('complete', () => emit('complete'));
animation.value.addEventListener('loopComplete', () => emit('loopComplete'));
animation.value.addEventListener('enterFrame', (e) => emit('enterFrame', e));
animation.value.addEventListener('segmentStart', (e) => emit('segmentStart', e));
animation.value.addEventListener('config_ready', () => emit('config_ready'));
animation.value.addEventListener('data_ready', () => emit('data_ready'));
animation.value.addEventListener('data_failed', (e) => emit('data_failed', e));
animation.value.addEventListener('loaded_images', () => emit('loaded_images'));
animation.value.addEventListener('DOMLoaded', () => emit('DOMLoaded'));
animation.value.addEventListener('destroy', () => emit('destroy'));
animation.value.addEventListener('error', (e) => emit('error', e));
};
// 监听属性变化
watch(() => props.speed, (newSpeed) => {
if (animation.value) {
animation.value.setSpeed(newSpeed);
}
}, { immediate: false });
watch(() => props.direction, (newDirection) => {
if (animation.value) {
animation.value.setDirection(newDirection);
}
}, { immediate: false });
watch(() => props.segments, (newSegments) => {
if (animation.value && newSegments) {
animation.value.playSegments(newSegments, true);
}
}, { immediate: false, deep: true });
// 监听动画数据变化,重新加载动画
watch(() => props.animationData, (newData) => {
if (animation.value) {
animation.value.destroy();
}
if (newData && container.value) {
initLottie();
}
}, { deep: true });
onMounted(() => {
initLottie();
});
onUnmounted(() => {
if (animation.value) {
// 移除所有事件监听
animation.value.removeEventListener('complete');
animation.value.removeEventListener('loopComplete');
animation.value.removeEventListener('enterFrame');
animation.value.removeEventListener('segmentStart');
animation.value.removeEventListener('config_ready');
animation.value.removeEventListener('data_ready');
animation.value.removeEventListener('data_failed');
animation.value.removeEventListener('loaded_images');
animation.value.removeEventListener('DOMLoaded');
animation.value.removeEventListener('destroy');
animation.value.removeEventListener('error');
// 销毁动画实例
animation.value.destroy();
animation.value = null;
}
});
</script>
<style scoped>
.lottie-container {
display: inline-block;
}
</style>
至此,我们已经封装好了一个完整的,并且功能完善的Lottie
组件。
3. 优化组件性能
为了进一步优化组件性能,我们可以考虑以下几点:
- 懒加载动画数据:对于较大的动画文件,可以考虑使用懒加载。
- 使用 Canvas 渲染器:对于复杂动画,Canvas 渲染器可能比 SVG 渲染器性能更好。
- 动态导入动画数据:使用
import()
动态导入动画数据。
NOTE
下面是一个优化示例:
<template>
<div class="lottie-container" ref="container" :style="containerStyle"></div>
</template>
<script setup>
import { ref, onMounted, onUnmounted, watch, computed } from 'vue';
// 定义组件属性
const props = defineProps({
//--------------------------------
// 与之前相同的属性......
src: {
type: String,
default: ''
}
});
//--------------------------------
// 与之前相同的代码......
// 动态加载动画数据
const loadAnimationData = async () => {
if (props.src) {
try {
const response = await fetch(props.src);
const data = await response.json();
if (animation.value) {
animation.value.destroy();
}
props.animationData = data;
initLottie();
} catch (error) {
console.error('Failed to load animation data:', error);
emit('error', error);
}
} else if (props.animationData) {
initLottie();
}
};
onMounted(() => {
if (props.src) {
loadAnimationData();
} else {
initLottie();
}
});
//--------------------------------
// 与之前相同的代码......
</script>
API 参考
下面是我们封装的 Lottie 组件的完整 API 参考。
属性 (Props)
属性名 | 类型 | 默认值 | 说明 |
---|---|---|---|
animationData | Object | 必填 | Lottie 动画数据,JSON 格式 |
loop | Boolean | true | 是否循环播放动画 |
autoplay | Boolean | true | 是否自动播放动画 |
width | String/Number | '100%' | 动画容器宽度 |
height | String/Number | '100%' | 动画容器高度 |
speed | Number | 1 | 动画播放速度,1 表示正常速度 |
direction | Number | 1 | 动画播放方向,1 表示正向,-1 表示反向 |
segments | Array | null | 播放特定片段,格式为 [开始帧, 结束帧] |
initialSegment | Array | null | 初始播放片段,格式为 [开始帧, 结束帧] |
renderer | String | 'svg' | 渲染器类型,可选值:'svg' 、'canvas' 、'html' |
rendererSettings | Object | {} | 渲染器设置,详见 lottie-web 文档 |
src | String | '' | 动画数据的 URL,如果提供,将从该 URL 加载动画数据 |
事件 (Events)
事件名 | 参数 | 说明 |
---|---|---|
complete | 无 | 动画播放完成时触发 |
loopComplete | 无 | 动画循环播放完成时触发 |
enterFrame | { currentTime, totalTime, ... } | 进入新帧时触发 |
segmentStart | { firstFrame, totalFrames, ... } | 片段开始播放时触发 |
config_ready | 无 | 配置就绪时触发 |
data_ready | 无 | 数据就绪时触发 |
data_failed | error | 数据加载失败时触发 |
loaded_images | 无 | 图像加载完成时触发 |
DOMLoaded | 无 | DOM 加载完成时触发 |
destroy | 无 | 动画实例销毁时触发 |
error | error | 发生错误时触发 |
方法 (Methods)
方法名 | 参数 | 返回值 | 说明 |
---|---|---|---|
play | 无 | 无 | 播放动画 |
pause | 无 | 无 | 暂停动画 |
stop | 无 | 无 | 停止动画并回到第一帧 |
setSpeed | speed: Number | 无 | 设置动画播放速度,speed为1表示正常速度 |
setDirection | direction: Number | 无 | 设置动画播放方向,1表示正向播放,-1表示反向播放 |
goToAndPlay | value: Number, isFrame: Boolean | 无 | 跳转到指定帧或时间并播放,isFrame为true 表示帧 ,为false 表示时间 ,默认为false |
goToAndStop | value: Number, isFrame: Boolean | 无 | 跳转到指定帧或时间并停止,isFrame为true 表示帧 ,为false 表示时间 ,默认为false |
setSegment | startFrame: Number, endFrame: Number | 无 | 设置播放片段 |
playSegments | segments: Array, forceFlag: Boolean | 无 | 播放指定片段,segments 可以包含两个数字或者两个数字组成的数组,forceFlag 表示是否立即强制播放该片段,例:anim.playSegments([[0,10],[30,45]], true) // 立即播放0-10帧和30-45帧 |
getDuration | inFrames: Boolean = true | Number | 获取动画持续时间,单位为帧或秒 |
访问 Lottie 实例
如果你需要访问原始的 Lottie 实例来使用更多高级功能,可以通过 lottieInstance
属性获取:
const lottieRef = ref(null);
// 在组件挂载后
onMounted(() => {
const lottieInstance = lottieRef.value?.lottieInstance.value;
if (lottieInstance) {
// 使用 lottieInstance 调用更多 lottie-web API
}
});
在本教程中,我们从零开始,一步步构建了一个功能完善的 Vue3 Lottie 组件。我们学习了:
- Lottie 的基本概念和工作原理
- 如何在 Vue3 中使用 lottie-web
- 如何封装一个基础的 Lottie 组件
- 如何添加事件处理、控制方法和配置选项
- 如何优化组件性能
- 组件的完整 API 和常见问题解决方案
通过这个组件,你可以在 Vue3 项目中轻松地使用 Lottie 动画,为你的应用添加生动、高质量的动画效果。
希望这个教程对你有所帮助!