React8+taro开发微信小程序,实现lottie动画

安装核心依赖

javascript 复制代码
npm install lottie-miniprogram @tarojs/plugin-html --save

修改 Taro 配置 (config/index.js)

javascript 复制代码
const config = {
  plugins: [
    '@tarojs/plugin-html',
    // 其他插件...
  ],
  mini: {
  	canvas: true,
    webpackChain(chain) {
      chain.merge({
        module: {
          rule: {
            'lottie-loader': {
              test: /\.json$/,
              use: {
                loader: 'lottie-miniprogram/webpack-loader',
                options: {
                  limit: 10240 // 10KB以下文件直接内联
                }
              }
            }
          }
        }
      })
    }
  }
}

app.config.js里添加配置

javascript 复制代码
export default defineAppConfig({
  // 其他代码...
  requiredBackgroundModes: ['canvas'],
    // 其他代码...
  })

封装组件commonLottie

javascript 复制代码
import React, { useEffect, useRef, useState } from 'react'
import Taro, { createSelectorQuery } from '@tarojs/taro'
import { View, Canvas } from '@tarojs/components'

const CommonLottie = React.forwardRef(
  (
    {
      animationData,
      width = 24,
      height = 24,
      loop = false,
      autoplay = true,
      canvasId = 'lottie-canvas' // 新增动态 ID 支持
    },
    ref
  ) => {
    const canvasRef = useRef(null)
    const lottieInstance = useRef(null)
    const animationDuration = useRef(1500) // 固定1秒时长

    // 暴露方法给父组件
    React.useImperativeHandle(ref, () => ({
      play: () => {
        if (lottieInstance.current) {
          // 先重置再播放
          lottieInstance.current.goToAndStop(0, true)
          lottieInstance.current.play()
        }
      },
      pause: () => lottieInstance.current?.pause()
    }))

    const calculateSizes = () => {
      //   const systemInfo = Taro.getSystemInfoSync()
      const dpr = 1

      // 逻辑尺寸 → 物理像素尺寸
      const physicalWidth = width * dpr
      const physicalHeight = height * dpr

      // Lottie 内部使用逻辑单位,需要反向缩放
      const lottieWidth = width / dpr
      const lottieHeight = height / dpr

      return {
        physicalWidth,
        physicalHeight,
        lottieWidth,
        lottieHeight,
        dpr
      }
    }
    // 使用重构后的尺寸配置
    const { physicalWidth, physicalHeight, lottieWidth, lottieHeight, dpr } =
      calculateSizes()

    // 微信小程序专属初始化逻辑
    const initWechatCanvas = async () => {
      try {
        // 使用 Taro.nextTick 确保 DOM 更新完成
        await new Promise((resolve) => Taro.nextTick(resolve))

        // 增加重试次数和间隔
        let retryCount = 0
        const MAX_RETRY = 5
        const getNode = () =>
          new Promise((resolve, reject) => {
            createSelectorQuery()
              .select(`#${canvasId}`)
              .fields({ node: true, size: true })
              .exec((res) => {
                if (res[0]?.node) resolve(res[0].node)
                else if (retryCount < MAX_RETRY) {
                  retryCount++
                  setTimeout(
                    () => getNode().then(resolve).catch(reject),
                    200 * retryCount
                  )
                } else {
                  reject(new Error(`Canvas 节点未找到 (ID: ${canvasId})`))
                }
              })
          })

        const node = await getNode()

        //动态加载 Lottie
        const Lottie = await import('lottie-miniprogram')
        // 计算播放速度
        const originalDuration =
          ((animationData.op - animationData.ip) / animationData.fr) * 1000
        const playSpeed = originalDuration / animationDuration.current
        //创建动画实例
        lottieInstance.current = Lottie.loadAnimation({
          canvas: node,
          renderer: 'canvas',
          animationData,
          loop,
          autoplay,
          rendererSettings: {
            context: node.getContext('2d'),
            dpr: 1,
            scaleMode: 2, // 1: 按比例填充容器,2: 完全填充
            preserveAspectRatio: 'xMidYMid meet' // 保持宽高比
          }
        })

        // 新增适配逻辑
        const animation = lottieInstance.current

        animation.resize(lottieWidth, lottieHeight) // 强制刷新尺寸
        animation.setSubframe(false) // 关闭子帧优化
        // 设置播放速度
        animation.setSpeed(playSpeed)

        // 自动销毁
        // animation.addEventListener('complete', () => {
        //   animation.destroy()
        // })
      } catch (err) {
        console.error('初始化失败:', err)
      }
    }

    useEffect(() => {
      if (process.env.TARO_ENV !== 'weapp') return
      if (!animationData) return

      const timer = setTimeout(() => {
        initWechatCanvas()
      }, 300) // 增加初始化延迟

      return () => {
        clearTimeout(timer)
        lottieInstance.current?.destroy()
      }
    }, [animationData])

    return (
      <View
        style={{
          width: `${width}px`,
          height: `${height}px`,
          overflow: 'visible',
          // 修复定位问题
          position: 'relative'
        }}
      >
        <Canvas
          id={canvasId}
          canvasId={canvasId} // 必须同时设置 id 和 canvasId
          type="2d"
          style={{
            width: `${physicalWidth}px`,
            height: `${physicalHeight}px`,
            transform: `translateZ(0)`, // 强制启用 GPU 加速
            transformOrigin: '0 0',
            imageSmoothingQuality: 'high' // 高清渲染
          }}
          ref={canvasRef}
        />
      </View>
    )
  }
)

export default CommonLottie

页面中使用

javascript 复制代码
import { View, Button, Image, Text, WebView } from '@tarojs/components'
import Taro from '@tarojs/taro'
import { useState, useEffect, useRef } from 'react'
import './index.module.less'
import styles from './index.module.less'
import animationData from '@/assets/doc/tab_2_on.json'
import CommonLottie from '@/components/commonLottie'

definePageConfig({
  navigationStyle: 'custom',
  backgroundColor: '#191919'
})

export default function ExperiencePage() {
  const lottieRef = useRef(null)
  const play = () => {
    lottieRef.current.play()
    console.log(' lottieRef.current', lottieRef.current)
  }
  return (
    <View className={styles['experience-wrap']}>
      <Button onClick={play}>按钮</Button>
      <CommonLottie
        ref={lottieRef}
        canvasId={'lottie-canvas-test'}
        animationData={animationData}
        width={36}
        height={24}
      />
    </View>
  )
}
相关推荐
阿桂有点桂1 小时前
React使用笔记(持续更新中)
前端·javascript·react.js·react
蒲公英源码2 小时前
基于PHP+Vue+小程序快递比价寄件系统
vue.js·小程序·php
小小王app小程序开发2 小时前
盲盒小程序一番赏创新玩法拓展:构建社交化集藏新生态
小程序
韩立学长4 小时前
【开题答辩实录分享】以《奇妙英语角小程序的设计与实现》为例进行答辩实录分享
小程序·php
在未来等你5 小时前
AI Agent设计模式 Day 2:Plan-and-Execute模式:先规划后执行的智能策略
设计模式·llm·react·ai agent·plan-and-execute
wx_ywyy67985 小时前
小程序定制开发实战:需求拆解、UI 设计与个性化功能落地流程
小程序·小程序开发·小程序制作·小程序搭建·小程序设计·小程序定制开发·小程序开发搭建
亮子AI5 小时前
【小程序】详细比较微信小程序的 onLoad 和 onShow
微信小程序·小程序
权泽谦5 小时前
用 Python 做一个天气预报桌面小程序(附源码 + 打包与部署指导)
开发语言·python·小程序
小小王app小程序开发6 小时前
盲盒抽赏小程序爬塔玩法分析:技术实现 + 留存破局,打造长效抽赏生态
小程序
阿里花盘6 小时前
教育培训机构如何搭建自己的在线教育小程序?
小程序·哈希算法·剪枝·霍夫曼树