我认为没有简单的解决方案。
但是可以停止无限循环,以新的速度执行动画的其余部分,然后再次启动无限循环。
我在这里实现了这个功能:
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>
</>
);
};