如何使用react-spring的useChain实现循环加载动画?
Answer
Great question! The core issue here is that useChain only runs once by default, and your current animation setup doesn't include logic to restart the sequence once it finishes. Let's break down two solid solutions to get your looping loading animation working.
Solution 1: Add loop logic to useChain manually
We can keep using useChain, but we'll add a listener to the last animation in the sequence to restart the chain once it completes. Here's how to adjust your code:
First, update your animation config to explicitly include loop behavior for each individual spring (we'll split the "scale up/down" into a looped animation instead of a multi-step to array):
const animation = { from: { scale: 0, opacity: 0, background: 'red' }, to: { scale: 1, opacity: 1 }, reverse: { scale: 0, opacity: 0 }, // Defines the "shrink back" state config: { duration: 500 }, loop: true // Makes each spring repeat its own animation }
Then, modify your component to restart the useChain sequence when the last animation finishes:
import { useSpring, useSpringRef, useChain, animated, useCallback, useEffect } from 'react-spring' const Loading = () => { const leftBottomRef = useSpringRef() const leftBottom = useSpring({ ref: leftBottomRef, ...animation }) const leftTwoRef = useSpringRef() const leftTwo = useSpring({ ref: leftTwoRef, ...animation }) const centerRef = useSpringRef() const center = useSpring({ ref: centerRef, ...animation }) const rightTwoRef = useSpringRef() const rightTwo = useSpring({ ref: rightTwoRef, ...animation }) const rightTopRef = useSpringRef() const rightTop = useSpring({ ref: rightTopRef, ...animation }) // Define a reusable function to start the chain sequence const startChainLoop = useCallback(() => { useChain( [leftBottomRef, leftTwoRef, centerRef, rightTwoRef, rightTopRef], [0, 0.3, 0.6, 0.9, 1.2], // Timing delays between each animation ) // Restart the chain when the last animation finishes its cycle rightTopRef.current.on('rest', startChainLoop) }, [leftBottomRef, leftTwoRef, centerRef, rightTwoRef, rightTopRef]) // Start the first chain loop when the component mounts useEffect(() => { startChainLoop() // Clean up the listener when the component unmounts return () => { rightTopRef.current.off('rest', startChainLoop) } }, [startChainLoop, rightTopRef]) return ( <div className='loading'> <div className='loader'> <animated.div style={center} /> <animated.div style={rightTwo} /> <animated.div style={rightTop} /> <animated.div style={leftTwo} /> <animated.div style={center} /> <animated.div style={rightTwo} /> <animated.div style={leftBottom} /> <animated.div style={leftTwo} /> <animated.div style={center} /> </div> loading... </div> ) }
Solution 2: Use useTrail for simpler sequential looping
If your goal is to animate multiple elements in a repeating sequence, useTrail is a better fit than useChain + individual useSpring calls. It's designed specifically for sequential animations and supports looping out of the box:
import { useTrail, animated } from 'react-spring' const Loading = () => { // Number of unique animation sequences (matches your 5 original refs) const trailCount = 5 // Create a trail of animations with built-in looping and delays const trail = useTrail(trailCount, { from: { scale: 0, opacity: 0, background: 'red' }, to: { scale: 1, opacity: 1 }, reverse: { scale: 0, opacity: 0 }, config: { duration: 500 }, loop: true, delay: 300, // Delay between each element's animation start }) return ( <div className='loading'> <div className='loader'> {/* Map your elements to the trail styles (match your original layout) */} <animated.div style={trail[2]} /> {/* Center */} <animated.div style={trail[3]} /> {/* Right Two */} <animated.div style={trail[4]} /> {/* Right Top */} <animated.div style={trail[1]} /> {/* Left Two */} <animated.div style={trail[2]} /> {/* Center */} <animated.div style={trail[3]} /> {/* Right Two */} <animated.div style={trail[0]} /> {/* Left Bottom */} <animated.div style={trail[1]} /> {/* Left Two */} <animated.div style={trail[2]} /> {/* Center */} </div> loading... </div> ) }
Key fixes for your original code:
- No built-in loop for
useChain:useChaindoesn't auto-repeat, so we need to manually listen for the last animation'srestevent to restart the sequence. - Multi-step
toarray issues: Your originaltoarray made each spring run two animations (scale up then down) once, but combining it withloop: truecaused timing conflicts. Splitting intotoandreversemakes the loop behavior predictable. - Shared animation states: Reusing the same
center/rightTwostyles across multiple elements made them animate in sync. UsinguseTrailor ensuring each element has its own ref avoids this.
内容的提问来源于stack exchange,提问作者menaka

