【问题标题】:Change animation duration without reset on framer-motion更改动画持续时间而不重置 framer-motion
【发布时间】:2021-12-07 15:43:06
【问题描述】:

我正在尝试通过更改过渡道具中的持续时间值来使用 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

【问题讨论】:

    标签: javascript framer-motion


    【解决方案1】:

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

    但是可以停止无限循环,以新的速度执行动画的其余部分,然后再次启动无限循环。

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

    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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2020-12-05
      • 1970-01-01
      • 2021-08-24
      • 1970-01-01
      • 1970-01-01
      • 2022-11-21
      • 2022-01-15
      • 2018-05-14
      相关资源
      最近更新 更多