Wondering how to run several different animations in sequence with React Native Reanimated

233 views Asked by At

When running multiple different animations at once, I want to specify the order of execution between each animation.

For example, in React Native, like the one below.

Reanimated also has a withSequence, but it doesn't seem to be able to handle the order of execution of multiple animations.

So I'm wondering if there's a way to specify the order of execution of different animations, like sequence and parallel in React native.

Animated Example

const a = useRef(new Animated.Value(0))
const b = useRef(new Animated.Value(1))


const let animate = () => {
  Animated.sequence(
    a->Animated.config(...)
    b->Animated.config(...)
  ).start()
}

Expected Example

const a = useSharedValue(0)
const b = useSharedValue(1)


const let animate = () => {
  withSequence(
    a->withTiming(...)
    b->withTiming(...)
  ).start()

 // or
  withParallel(
    a->withTiming(...)
    b->withTiming(...)
  ).start()
}
1

There are 1 answers

0
Michael Bahl On

I adapted my example:

import React from 'react';
import { StyleSheet, View } from 'react-native';
import Animated, {
  useSharedValue,
  useAnimatedStyle,
  withTiming,
  Easing,
  runOnJS,
} from 'react-native-reanimated';

const initialOffset = 200;

interface Move {
  x?: {
    duration?: number;
    distance?: number;
  };
  y?: {
    duration?: number;
    distance?: number;
  };
}

class ChainableAnimation {
  constructor() {
    this.defaultDuration = 800;
    this.animationSequence = Array<Move>();
  }
  addAnimation(move: Move = { x: { distance: 50 } }) {
    this.animationSequence.push(move);
  }
  createPromise({ move, offset }: { move: Move; offset: any }) {
    return new Promise<void>((resolve) => {
      Object.keys(move).forEach((key) => {
        const { distance, duration } = move[key];

        const animation = withTiming(
          distance,
          {
            duration: duration || this.defaultDuration,
            easing: Easing.cubic,
          },
          (_finished) => {
            console.log('_finished: ', _finished);
            runOnJS(resolve)();
          }
        );
        offset[key].value = animation;
      });
    });
  }
  startAnimation(offset) {
    this.animationSequence.reduce((previousPromise, sequence) => {
      return previousPromise.then(() => {
        return this.createPromise({
          move: sequence,
          offset,
        });
      });
    }, Promise.resolve());
  }
}

export default function App() {
  const offsetX = useSharedValue(initialOffset);
  const offsetY = useSharedValue(initialOffset);

  const offsetXTop = useSharedValue(initialOffset);
  const offsetYTop = useSharedValue(initialOffset);

  const offset = { x: offsetX, y: offsetY };
  const offsetTop = { x: offsetXTop, y: offsetYTop };

  const animatedStyles = useAnimatedStyle(() => ({
    transform: [{ translateX: offset.x.value }, { translateY: offset.y.value }],
  }));

  const animatedStylesTop = useAnimatedStyle(() => ({
    transform: [
      { translateX: offsetTop.x.value },
      { translateY: offsetTop.y.value },
    ],
  }));

  React.useEffect(() => {
    // ChainableAnimation.duration = 1300;
    const chainableAnimation = new ChainableAnimation();
    chainableAnimation.addAnimation({
      x: { distance: 100 },
      y: { distance: -200 },
    });
    chainableAnimation.addAnimation({
      x: { distance: -100 },
      y: { distance: -100 },
    });
    chainableAnimation.addAnimation({ y: { distance: -100 } });
    chainableAnimation.addAnimation({ x: { distance: 100 } });
    chainableAnimation.addAnimation({ x: { distance: -100, duration: 3000 } });
    chainableAnimation.addAnimation({ y: { distance: 100 } });
    chainableAnimation.addAnimation({
      x: { distance: -100 },
      y: { distance: -100 },
    });

    chainableAnimation.startAnimation(offset);

    const chainableAnimationTop = new ChainableAnimation();
    chainableAnimationTop.addAnimation({
      x: { distance: 50 },
      y: { distance: -100 },
    });
    chainableAnimationTop.addAnimation({
      x: { distance: -50 },
      y: { distance: -50 },
    });
    chainableAnimationTop.addAnimation({ y: { distance: -50 } });
    chainableAnimationTop.addAnimation({ x: { distance: 500 } });
    chainableAnimationTop.addAnimation({
      x: { distance: -50, duration: 1500 },
    });
    chainableAnimationTop.addAnimation({ y: { distance: 50 } });
    chainableAnimationTop.addAnimation({
      x: { distance: -50 },
      y: { distance: -50 },
    });

    chainableAnimationTop.startAnimation(offsetTop);
  }, []);

  return (
    <View style={styles.container}>
      <Animated.View style={[styles.box, animatedStyles]} />
      <Animated.View style={[styles.box, animatedStylesTop]} />
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    alignItems: 'center',
    justifyContent: 'center',
    height: '100%',
  },
  box: {
    height: 120,
    width: 120,
    backgroundColor: '#b58df1',
    borderRadius: 20,
  },
});