跳到主要内容
极客日志极客日志面向AI+效率的开发者社区
首页博客GitHub 精选镜像工具UI配色美学隐私政策关于联系
搜索内容 / 工具 / 仓库 / 镜像...⌘K搜索
注册
博客列表
JavaScript大前端

Lottie-Web 动画开发技术指南

综述由AI生成Lottie-Web 动画库的使用指南。涵盖安装引入、基础用法、API 详解及 Vue 集成实战。重点讲解了 SVG 与 Canvas 渲染器选择、性能优化策略如懒加载和按需加载,以及常见问题解决方案。适合前端开发者在 Web 项目中实现高质量矢量动画。

Kubernet发布于 2026/4/6更新于 2026/5/2022 浏览

一、什么是 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 # 或 yarn add lottie-web
3.2 CDN 引入
<!-- 通过 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 项目引入
// main.js 或组件中
import lottie from 'lottie-web'
// 挂载到 Vue 原型(可选)
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', // 渲染方式:'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: {
    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 = 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] // 从第 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 预加载动画
// 预加载动画数据
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 选择合适的渲染器
// 简单动画:使用 SVG(推荐)
renderer: 'svg'
// 复杂动画或大量动画:使用 Canvas
renderer: 'canvas'
// 避免使用 HTML 渲染器(性能较差)
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 动画不显示

问题:动画容器存在,但动画不显示

解决方案:

// 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 而不是 path
const response = await fetch('/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 错误处理
// 完善的错误处理
const initAnimation = async () => {
  try {
    // 1. 验证容器
    if (!container) {
      throw new Error('容器元素不存在')
    }
    // 2. 验证数据源
    if (!animationData && !path) {
      throw new Error('请提供 animationData 或 path')
    }
    // 3. 加载动画数据
    let data = animationData
    if (!data && path) {
      const response = await fetch(path)
      if (!response.ok) {
        throw new Error(`加载失败:${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.ts
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 数据可视化动画
// 数字增长动画配合 Lottie
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
    // 播放对应的 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 的完整使用指南,从基础到高级,从理论到实践。建议按照顺序阅读,并根据实际项目需求选择合适的方案.

目录

  1. 一、什么是 Lottie-Web
  2. 1.1 Lottie 简介
  3. 1.2 核心优势
  4. 1.3 工作原理
  5. 二、为什么选择 Lottie-Web
  6. 2.1 传统动画方案对比
  7. 2.2 适用场景
  8. 三、安装与引入
  9. 3.1 NPM 安装(推荐)
  10. 3.2 CDN 引入
  11. 3.3 ES6 模块引入
  12. 3.4 Vue 项目引入
  13. 四、基础使用
  14. 4.1 最简单的示例
  15. 4.2 使用本地 JSON 文件
  16. 4.3 三种渲染方式对比
  17. SVG 渲染(推荐)
  18. Canvas 渲染
  19. HTML 渲染
  20. 五、API 详解
  21. 5.1 loadAnimation() 配置选项
  22. 5.2 动画实例方法
  23. 播放控制
  24. 属性获取
  25. 事件监听
  26. 其他方法
  27. 5.3 全局方法
  28. 六、Vue 集成实战
  29. 6.1 Vue 2.x 组件封装(Options API)
  30. 6.2 Vue 2.x 使用示例
  31. 6.3 Vue 3 Composition API 封装
  32. 6.4 移动端 H5 集成示例(Vant + Vue 2)
  33. 七、高级特性
  34. 7.1 动态替换动画
  35. 7.2 片段播放
  36. 7.3 响应式调整
  37. 7.4 性能优化渲染设置
  38. 7.5 预加载动画
  39. 八、性能优化
  40. 8.1 选择合适的渲染器
  41. 8.2 按需加载
  42. 8.3 使用轻量版本
  43. 8.4 优化动画文件
  44. 8.5 控制动画数量
  45. 8.6 使用 Intersection Observer 懒加载
  46. 九、常见问题与解决方案
  47. 9.1 动画不显示
  48. 9.2 动画模糊
  49. 9.3 动画播放不流畅
  50. 9.4 跨域问题
  51. 9.5 内存泄漏
  52. 9.6 动画无法控制
  53. 十、最佳实践
  54. 10.1 组件封装建议
  55. 10.2 错误处理
  56. 10.3 类型定义(TypeScript)
  57. 十一、实际应用场景
  58. 11.1 加载动画(Loading)
  59. 11.2 页面过渡动画
  60. 11.3 交互动画反馈
  61. 11.4 数据可视化动画
  62. 11.5 空状态动画
  63. 十二、总结
  64. 12.1 核心要点
  65. 12.2 学习资源
  66. 12.3 相关工具
  67. 附录:完整示例代码
  68. A. 完整的 Vue 2 组件
  • 💰 8折买阿里云服务器限时8折了解详情
  • Magick API 一键接入全球大模型注册送1000万token查看
  • 🤖 一键搭建Deepseek满血版了解详情
  • 一键打造专属AI 智能体了解详情
极客日志微信公众号二维码

微信扫一扫,关注极客日志

微信公众号「极客日志V2」,在微信中扫描左侧二维码关注。展示文案:极客日志V2 zeeklog

更多推荐文章

查看全部
  • JavaSE 网络协议:HTTP 请求与响应解析
  • OSGEarth 在 Qt C++ 中显示三维地球经纬度
  • VSCode Copilot 配置文件提示未知工具警告解决方案
  • 算法面试高频考点与解题模板避坑指南
  • JavaScript 能否同时胜任前后端开发
  • Linux 进程管理进阶:会话、进程组与守护进程的底层逻辑与实践
  • JavaScript 数组模拟栈与队列数据结构
  • CS336 从零构建语言模型:Transformer LM 架构实现
  • 数据结构:跳表(Skiplist)原理与实现
  • JavaScript filter 方法详解与实战
  • 特殊儿童干预:基于 VoxCPM-1.5-TTS 的辅助沟通系统实践
  • 基于 Coze 的 AI 应用开发:从智能体到 Web 部署
  • 大模型微调技术详解与实战代码实现
  • Java 高频面试题汇总与核心知识点解析
  • Java 中高级面试核心考点解析:集合与线程同步
  • OpenClaw Gateway Windows 开机自启与 Dashboard 自动打开方案
  • JavaScript 文档对象核心属性实战指南
  • AI 产品经理面试通关:100 道高频问题深度解析与核心知识点梳理
  • DeepSeek 中冷启动数据与多阶段训练的作用
  • Spring Boot 自动配置原理与实战详解

相关免费在线工具

  • 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