1

React-Native Animated: How to loop an animation without resetting the animation value after each loop.

I was struggling to figure out a method of looping React Native Animations without resetting the animated value back to its initial value.

For example I made a simple "Sway" animation that would move my component left and right

  let animationX = new Animated.Value(0);

  const left = (toValue) => Animated.timing(animationX, {toValue: -toValue, duration: 1500})
  const right = (toValue) => Animated.timing(animationX, {toValue, duration: 1500})

  const startSwayAnimation = () => {
    Animated.loop(
      Animated.sequence([
        left(100),
        right(100),
      ])
    ).start()
  }

The problem was that my animationX variable resets back to 0 after every loop. I couldn't find any useful resources on how to loop an animation without resetting the value, though there are two dead issues on github:

https://github.com/facebook/react-native/issues/18028 https://github.com/facebook/react-native/issues/20560

I was nearly at the point of considering asking on SO, but then I tried creating a recursive function to loop the animation instead and it worked great

In case anyone else is struggling with this, or knows a better solution (perhaps some config for Animated.loop that I am unaware of) I decided to share this on SO.

2 Answers 2

1

Memory Leak Fix for above answer!!

My previous implementation resulted in a memory leak I also figured out how to make it reusable in a custom hook!

const useLoopAnimation = (animationFn) => {
  // loopAnimation halts when changing state variables (using useState) for unknown reason
  // so use a let variable instead.
  let stopped = false;
  const loopAnimation = () => {
    if (stopped) return false;
    animationFn().start(() => loopAnimation());
  };

  // this runs on component dismount to stop the recursive loop.
  useEffect(() => () => {
    stopped = true;
  });
  return loopAnimation;
};

and then in your component you use it like so

  const loopAnimation = useLoopAnimation(() =>
    Animated.sequence([left(100), right(100)])
  );
Sign up to request clarification or add additional context in comments.

Comments

0

You can use recursion to call the same Animation over and over again by calling the same function in the start callback

  let animationX = new Animated.Value(0);
  const left = (toValue: number) =>
    Animated.timing(animationX, { toValue: -toValue, duration: 1500 });
  const right = (toValue: number) =>
    Animated.timing(animationX, { toValue, duration: 1500 });

    const loopSway = () => {
        Animated.sequence([
          left(100),
          right(100),
        ]).start(() => loopSway())
    }

However, I was unable to create a reusable loopAnimation function. when I try, I get the error undefined is not an object (evaluating 'animations[current].start')

    const sway = Animated.sequence([
      left(100),
      right(100),
    ])

    const loopAnimation = (animation) => {
        animation.start(() => loopAnimation(animation))
    }

    const loopSway = loopAnimation(sway)

It appears there's some side effect in React Native Animated that doesn't let you assign animations as constants :(

I tried cloning the animation

    const loopAnimation = (animation) => {
        const animationClone = {...animation}
        animation.start(() => loopAnimation(animationClone))
    }

and I tried making the animation a state value.

But didn't have any success with either so would appreciate if anyone has insight on how to make this fn reusable!

1 Comment

It turns out this results in a memory leak, I'm working on a solution!

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.