更改动画持续时间而不重置 framer-motion

Change animation duration without reset on framer-motion

我正在尝试通过更改 transition 道具中的持续时间值来使用 framer-motion 减慢无限动画。我正在使用 useMotionValue 钩子和动画函数:

const [count, setCount] = useState(1);

const scale = useMotionValue(1);

React.useEffect(() => {
  // the [1, 2] is for [from, to]
  const controls = animate(scale, [1, 2], {
    repeat: Infinity,
    ease: "linear",
    duration: count
  });

  return controls.stop;
}, [scale, count]);

这里的问题是我的动画从头重置。

如果我没有设置任何 from 值 (animate(scale, 2, ...)),它不会重置,但下一次重复的开始将是持续时间更改之前的最后一个值。 (例如,我在缩放值为 1.5 时重置,我的动画将在 1.5 和 2 之间循环,而不是 1 和 2)

我在这里重现了:https://codesandbox.io/s/framer-motion-simple-animation-forked-n8pbb?file=/src/index.tsx

我认为没有简单的解决方案。

但是可以停止无限循环,以新的速度执行剩余的动画,然后再次开始无限循环。

我在这里实现了这个功能:

const App = () => {
  const [count, setCount] = useState(1); // speed of animation

  const scale = useMotionValue(1); // the animating motion value

  // when we increase count, this will be set to true
  // and it will finish the remaining part of the animation
  const hasToFinish = useRef(false);

  const [triggerRerender, setTriggerRerender] = useState(false); // this is for triggering a rerender - see implementation below

  const performResetAnimation = useRef(false); // this is for performing an animation from scale 2 to scale 1

  React.useEffect(() => {
    let controls;
    // check if the animation has to finish, after count has been increased
    if (hasToFinish.current) {
      if (!performResetAnimation.current) {
        // check the target of the running animation - bigger or smaller
        if (scale.getPrevious() >= scale.get()) {
          // it's getting smaller
          // finish the animation from current scale to 1
          controls = animate(scale, [scale.get(), 1], {
            ease: "linear",
            duration: count * (scale.get() - 1), // calculate remaining duration with new speed
            onComplete: () => {
              hasToFinish.current = false;
              setTriggerRerender(!triggerRerender); // then trigger a rerender to go back to the infinite animation
            }
          });
        } else {
          // it's getting bigger
          // finish the animation from current scale to 2
          controls = animate(scale, [scale.get(), 2], {
            ease: "linear",
            duration: count * (2 - scale.get()), // calculate remaining duration with new speed
            onComplete: () => {
              // it has to animate back once to scale 1 because the infinite animation starts there
              performResetAnimation.current = true;
              setTriggerRerender(!triggerRerender); // trigger rerender to go to the reset animation
            }
          });
        }
      } else {
        // perform reset animation
        // if the count is increased while the reset animation plays, it should just proceed as usual
        // and not go into the reset animation again, so we set performResetAnimation to false
        performResetAnimation.current = false;
        controls = animate(scale, [2, 1], {
          ease: "linear",
          duration: count,
          onComplete: () => {
            hasToFinish.current = false;
            setTriggerRerender(!triggerRerender); // trigger rerender to go back to infinite animation
          }
        });
      }
    } else {
      // if it doesn't have to finish, perform the infinite animation
      controls = animate(scale, [1, 2], {
        repeat: Infinity,
        repeatType: "reverse",
        ease: "linear",
        duration: count
      });
    }

    return controls.stop;
  }, [triggerRerender, scale, count]);

  return (
    <>
      <Add
        onClick={() => {
          hasToFinish.current = true; // set hasToFinish to true, so that it finishes the animation with the new duration
          setCount(count + 1); // triggers a rerender with new duration
        }}
      />
      <div className="count">{count}</div>
      <div className="example-container">
        <motion.div style={{ scale }} key={count} />
      </div>
    </>
  );
};

Check this forked codesandbox out