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?
- Closure Trap: In your
setTimeoutcallback,isLoadingis captured from the initial render's closure. OnceisLoadingturnsfalse, the callback still sees the oldtruevalue, so it keeps callingloadingAnimation()infinitely. - Infinite CSS Animation: Your CSS sets
animation-iteration-count: infinite, so even if you toggle theanimatestate 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:
isLoadingRefensures 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
相关产品推荐
相关产品推荐

