更改动画持续时间而不重置 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
我正在尝试通过更改 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>
</>
);
};