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>
)
}