【JS】Lottie动画时出现闪烁问题

// 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. 使用Lottieweb版本

  • 使用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动画的闪烁问题。

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容