// Lottie.tsx
import React, {
CSSProperties,
useEffect,
useImperativeHandle,
useRef,
} from "react";
import Lottie, { AnimationItem, AnimationConfig } from "lottie-web";
import classNames from "classnames";
export interface LottieProps extends Omit<AnimationConfig, "container"> {
width: number | string;
height: number | string;
className?: string;
style?: CSSProperties;
animationData?: any;
path?: string;
single?: boolean;
onComplete?: () => void;
onClick?: (event: React.MouseEvent<HTMLDivElement>) => void;
onLoad?: (instance: AnimationItem) => void;
}
function getInstance() {
let __instance: AnimationItem | null = null;
return (
id: string,
opt: Partial<LottieProps>,
onComplete?: () => void,
onLoad?: (ins: AnimationItem) => void,
) => {
if (!opt.single || !__instance) {
const ele = document.querySelector(`#${id}`);
__instance = Lottie.loadAnimation({
container: ele as Element, // the dom element that will contain the animation
renderer: "svg",
...opt,
});
onLoad?.(__instance);
if (onComplete) {
__instance.addEventListener("complete", onComplete);
}
}
return {
instance: __instance,
destroy: () => {
__instance = null;
},
};
};
}
const _getInstance = getInstance();
export default React.forwardRef(
(props: LottieProps & Partial<AnimationItem>, ref) => {
const {
className,
style,
width,
height,
onComplete,
onLoad,
onClick,
...opt
} = props;
const lottieInstance = useRef<AnimationItem | null>(null);
const optRef = useRef(opt);
const randomId = useRef<string>(`n-canvas__${+new Date()}`);
const funcRef = useRef<{
onComplete?: () => void;
onLoad?: (instance: AnimationItem) => void;
}>({
onComplete,
onLoad,
});
useImperativeHandle(
ref,
() => {
return {
getInstance: () => lottieInstance.current,
};
},
[],
);
useEffect(() => {
const { instance, destroy } = _getInstance(
randomId.current,
optRef.current,
funcRef.current.onComplete,
funcRef.current.onLoad,
);
if (!lottieInstance.current) {
lottieInstance.current = instance;
}
return () => {
// 清除单例 【StrictMode 下多次触发】
lottieInstance.current?.destroy();
destroy();
};
}, []);
return (
<div
className={classNames("n-lottie-web", className)}
style={style}
onClick={onClick}
>
<div style={{ width, height }} id={randomId.current} />
</div>
);
},
);
// xx.tsx
<Lottie
animationData={firework}
width={window.innerWidth}
height="auto"
className={cn("fixed left-0 bottom-0 right-0")}
onClick={(e) => {
e.stopPropagation();
e.preventDefault();
}}
loop={false}
onComplete={() => {
onFeedbackPlayEnd?.();
}}
/>
播放Lottie动画时出现闪烁问题,可能是由于硬件性能、渲染引擎或动画本身的复杂性导致的。以下是一些可能的解决方案:
1. 优化动画数据
- 减少图层数量:检查动画中的图层数量,尽量减少不必要的图层。过多的图层会增加渲染负担,导致性能问题。
- 简化路径和形状:如果动画中有复杂的路径或形状,尝试简化它们。复杂的路径会增加渲染时间,导致闪烁。
- 减少关键帧:减少动画中的关键帧数量,尤其是在复杂的动画中。过多的关键帧可能会导致渲染不流畅。
2. 调整Lottie配置
-
降低渲染质量:Lottie提供了一个
rendererSettings
选项,可以调整渲染质量。你可以尝试降低渲染质量以减少性能开销。<Lottie animationData={firework} rendererSettings={{ preserveAspectRatio: 'xMidYMid slice', clearCanvas: true, progressiveLoad: true, hideOnTransparent: true, }} width={window.innerWidth} height="auto" className={cn("fixed left-0 bottom-0 right-0")} onClick={(e) => { e.stopPropagation(); e.preventDefault(); }} loop={false} onComplete={() => { onFeedbackPlayEnd?.(); }} />
-
使用
shouldReduceMotion
:如果用户开启了减少动画的选项,可以通过shouldReduceMotion
来减少动画的复杂度或禁用动画。
3. 硬件加速
-
启用硬件加速:确保动画元素启用了硬件加速。你可以通过CSS属性
transform: translateZ(0)
或will-change: transform
来强制启用硬件加速。.lottie-container { transform: translateZ(0); will-change: transform; }
4. 减少动画的复杂度
- 分段播放:如果动画非常复杂,可以尝试将动画分成多个部分,分段播放。这样可以减少单次渲染的负担。
- 降低帧率:如果动画的帧率过高,可以尝试降低帧率。Lottie动画默认帧率为60fps,你可以尝试降低到30fps。
5. 使用useRef
优化渲染
-
避免不必要的重新渲染:确保Lottie组件不会因为父组件的状态变化而重新渲染。你可以使用
useRef
来避免不必要的重新渲染。const lottieRef = useRef(null); <Lottie ref={lottieRef} animationData={firework} width={window.innerWidth} height="auto" className={cn("fixed left-0 bottom-0 right-0")} onClick={(e) => { e.stopPropagation(); e.preventDefault(); }} loop={false} onComplete={() => { onFeedbackPlayEnd?.(); }} />
6. 检查设备性能
- 设备性能问题:小米平板的硬件性能可能有限,尤其是在处理复杂动画时。你可以尝试在其他设备上播放动画,看看是否也有类似问题。如果问题只在小米平板上出现,可能是设备性能问题。
7. 使用Lottie
的web
版本
-
使用
lottie-web
:如果你使用的是react-lottie
,可以尝试直接使用lottie-web
库,它提供了更多的配置选项和更好的性能控制。import lottie from 'lottie-web'; import { useEffect, useRef } from 'react'; const LottieAnimation = ({ animationData }) => { const containerRef = useRef(null); useEffect(() => { const anim = lottie.loadAnimation({ container: containerRef.current, renderer: 'svg', loop: false, autoplay: true, animationData, }); return () => anim.destroy(); }, [animationData]); return <div ref={containerRef} style={{ width: '100%', height: 'auto' }} />; }; export default LottieAnimation;
8. 检查动画文件
- 检查动画文件是否有问题:有时动画文件本身可能存在问题,导致在某些设备上播放不正常。你可以尝试使用Lottie的官方工具(如LottieFiles)来检查动画文件是否有问题。
9. 使用requestAnimationFrame
-
手动控制动画帧:如果你对性能有更高的要求,可以尝试使用
requestAnimationFrame
手动控制动画的帧率,以减少闪烁。
10. 降级到Canvas渲染
-
使用Canvas渲染:如果SVG渲染在小米平板上存在问题,可以尝试将Lottie的渲染器切换到Canvas模式。Canvas在某些设备上可能有更好的性能表现。
<Lottie animationData={firework} renderer="canvas" width={window.innerWidth} height="auto" className={cn("fixed left-0 bottom-0 right-0")} onClick={(e) => { e.stopPropagation(); e.preventDefault(); }} loop={false} onComplete={() => { onFeedbackPlayEnd?.(); }} />
最终选用的方案3,消除小米平板上Lottie动画的闪烁问题。