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

使用useSpring时组件重渲染导致动画链重置及其他动画逐渐卡顿问题

Understanding and Fixing Your react-spring Animation Issues

Let's break down the problems you're facing with your looped spring animation, and walk through solutions for each one.

1. Why does the animation reset on re-render?

react-spring's useSpring hook relies on reference equality for the from and to props to decide if it should reset the animation. When you define the to array inline without stabilizing its reference, every re-render creates a brand new array. react-spring interprets this as a "new animation target" and resets the sequence to start from the first to entry.

Stabilizing the to array with useMemo fixes the reset issue, but introduces the phase-skipping problem. Here's why: with a stable reference, react-spring checks the current animation state against the to array. If the current state matches the first entry (marginBottom: 0), it assumes that phase is already complete and jumps straight to the next step in the sequence—skipping the upward move if the re-render happens right before that phase was supposed to start.

2. Fixing skipped phases and unwanted resets

The most reliable way to handle sequential, looped animations without these issues is to use react-spring's function-based loop syntax instead of an array. This approach keeps the animation sequence persistent across re-renders, ensuring phases never get skipped and the animation doesn't reset unexpectedly.

Here's how to rewrite your animation:

import { useSpring, animated } from '@react-spring/web';

function YourComponent() {
  const style = useSpring({
    from: { marginBottom: 0 },
    loop: async (next, cancel) => {
      // Wait 3 seconds before starting the upward move
      await new Promise(resolve => setTimeout(resolve, 3000));
      // Animate up to marginBottom: 80
      await next({ marginBottom: 80, config: { duration: 600 } });
      // Instantly jump down to marginBottom: -80
      await next({ marginBottom: -80, config: { duration: 0 } });
      // Wait 1 second before animating back to start
      await new Promise(resolve => setTimeout(resolve, 1000));
      // Animate back to marginBottom: 0
      await next({ marginBottom: 0, config: { duration: 600 } });
      // Return true to repeat the loop
      return true;
    }
  });

  return <animated.div style={style}>Your animated content</animated.div>;
}

This async-based loop maintains its state across re-renders—react-spring won't skip phases or reset the animation just because your component re-renders.

3. Why does repeated interrupts cause performance decay?

The slowdown you're seeing is likely due to uncleaned animation instances or accumulated scheduler tasks. Every time your component re-renders and creates a new to array, react-spring cancels the existing animation and spins up a new one. Over repeated interrupts, this can leave behind orphaned animation objects or flood the internal scheduler with canceled tasks, which drags down performance for other resource-heavy animations.

Using the function-based loop (as shown above) or properly stabilizing your to array (and adjusting the sequence to avoid phase skipping) prevents this. When the animation reference is stable, react-spring doesn't create new animation instances on re-render, so no orphaned tasks accumulate over time.

Quick Additional Tip

If you prefer to stick with the array-based to syntax, you can combine useMemo with a useRef to track the current animation phase. On re-render, you can adjust the to array to start from the current phase instead of the beginning—though this is more complex than the function-based loop approach.

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.28 18:59:09