You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

React加载动画完整周期控制逻辑实现求助:解决无限动画问题并适配加载时长需求

Fixing Infinite Animation in React Loader Component with Complete Cycle Requirement

Got it, let's break down what's causing the infinite animation loop and fix it to meet your requirement of finishing full animation cycles even after data loads.

What's Wrong with the Original Code?

  1. Closure Trap: In your setTimeout callback, isLoading is captured from the initial render's closure. Once isLoading turns false, the callback still sees the old true value, so it keeps calling loadingAnimation() infinitely.
  2. Infinite CSS Animation: Your CSS sets animation-iteration-count: infinite, so even if you toggle the animate state off, the animation will keep running as long as the class is applied.

Step-by-Step Fix

We'll adjust both the React component logic and CSS to ensure animations complete full cycles before stopping, even if data finishes loading mid-cycle.

Modified React Component

import { useEffect, useState, useRef } from 'react';
import { useAppSelector } from '../../hooks/useRedux';
import './style.css';

const Loader = () => {
  const isLoading = useAppSelector((state) => state.appSlice.isLoading);
  const [animate, setAnimate] = useState(false);
  // Use refs to track latest isLoading state and cleanup timers
  const isLoadingRef = useRef(isLoading);
  const animationTimerRef = useRef(null);

  // Sync ref with latest isLoading value to avoid closure issues
  useEffect(() => {
    isLoadingRef.current = isLoading;
  }, [isLoading]);

  useEffect(() => {
    // Calculate full animation cycle duration: longest item's delay + animation length
    // Item 3 has 0.6s delay + 1.5s animation = 2.1s total for a full sequence
    const fullCycleDuration = 2100;

    const startAnimationCycle = () => {
      // Trigger the animation for this cycle
      setAnimate(true);
      
      // Wait for the full cycle to finish before checking loading state
      animationTimerRef.current = setTimeout(() => {
        if (isLoadingRef.current) {
          // Still loading: reset state briefly to restart the animation
          setAnimate(false);
          // Small delay to let DOM update before starting next cycle
          setTimeout(startAnimationCycle, 50);
        } else {
          // Loading done: stop the animation after this cycle
          setAnimate(false);
        }
      }, fullCycleDuration);
    };

    if (isLoading) {
      // Start the animation sequence when loading begins
      startAnimationCycle();
    } else {
      // If loading finishes mid-cycle, wait for the current cycle to end before stopping
      if (animate) {
        clearTimeout(animationTimerRef.current);
        animationTimerRef.current = setTimeout(() => {
          setAnimate(false);
        }, fullCycleDuration);
      }
    }

    // Cleanup timers to prevent memory leaks
    return () => {
      if (animationTimerRef.current) {
        clearTimeout(animationTimerRef.current);
      }
    };
  }, [isLoading]);

  return (
    <div className='loader'>
      <ul className='loader__items'>
        <li className={`${animate ? 'loader__item-1' : ''}`}></li>
        <li className={`${animate ? 'loader__item-2' : ''}`}></li>
        <li className={`${animate ? 'loader__item-3' : ''}`}></li>
      </ul>
    </div>
  );
};

export default Loader;

Modified CSS

We'll change the animation to run only once per cycle, so we can control restarting it via React state:

.loader {
  display: flex;
  height: 22px;
}

.loader__items {
  display: flex;
  flex-direction: row;
  align-items: center;
  justify-content: center;
  width: 100%;
}

.loader ul li {
  width: 8px;
  height: 8px;
  margin: 2.5px;
  background-color: #47535d;
  border-radius: 100%;
}

.loader__item-1, .loader__item-2, .loader__item-3 {
  transition-timing-function: linear;
  animation-name: jump;
  animation-duration: 1.5s;
  animation-iteration-count: 1; /* Run animation only once per class application */
}

.loader__item-2 {
  animation-delay: 0.3s;
}

.loader__item-3 {
  animation-delay: 0.6s;
}

@keyframes jump {
  0% { transform: translateY(0); }
  50% { transform: translateY(-5px); }
  100% { transform: translateY(0); }
}

Key Improvements

  • Ref for Latest State: isLoadingRef ensures we always check the current loading status in the timer callback, avoiding the closure trap.
  • Full Cycle Control: We wait for the entire animation sequence (including delays) to finish before deciding to restart or stop.
  • Cleanup: Added timer cleanup to prevent memory leaks when the component unmounts or loading state changes.
  • CSS Adjustment: By setting animation-iteration-count: 1, we let React control when animations restart, eliminating infinite CSS loops.

If you need to adjust the full cycle duration to exactly 2 seconds (as mentioned in your requirement), tweak the animation duration and delays in CSS to add up to 2s, then update fullCycleDuration to 2000.

内容的提问来源于stack exchange,提问作者Vladislav

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.27 17:07:34