react-native-reanimated
Version:
More powerful alternative to Animated library for React Native.
169 lines (152 loc) • 5.59 kB
text/typescript
import type {
AnimatableValue,
Animation,
AnimationObject,
ReduceMotion,
Timestamp,
} from '../commonTypes';
import { logger } from '../logger';
import type { NextAnimation, SequenceAnimation } from './commonTypes';
import { defineAnimation, getReduceMotionForAnimation } from './util';
/**
* Lets you run animations in a sequence.
*
* @param reduceMotion - Determines how the animation responds to the device's
* reduced motion accessibility setting. Default to `ReduceMotion.System` -
* {@link ReduceMotion}.
* @param animations - Any number of animation objects to be run in a sequence.
* @returns An [animation
* object](https://docs.swmansion.com/react-native-reanimated/docs/fundamentals/glossary#animation-object)
* which holds the current state of the animation/
* @see https://docs.swmansion.com/react-native-reanimated/docs/animations/withSequence
*/
export function withSequence<T extends AnimatableValue>(
_reduceMotion: ReduceMotion,
...animations: T[]
): T;
export function withSequence<T extends AnimatableValue>(...animations: T[]): T;
export function withSequence(
_reduceMotionOrFirstAnimation?: ReduceMotion | NextAnimation<AnimationObject>,
..._animations: NextAnimation<AnimationObject>[]
): Animation<SequenceAnimation> {
'worklet';
let reduceMotion: ReduceMotion | undefined;
// the first argument is either a config or an animation
// this is done to allow the reduce motion config prop to be optional
if (_reduceMotionOrFirstAnimation) {
if (typeof _reduceMotionOrFirstAnimation === 'string') {
reduceMotion = _reduceMotionOrFirstAnimation as ReduceMotion;
} else {
_animations.unshift(
_reduceMotionOrFirstAnimation as NextAnimation<AnimationObject>
);
}
}
if (_animations.length === 0) {
logger.warn('No animation was provided for the sequence');
return defineAnimation<SequenceAnimation>(0, () => {
'worklet';
return {
onStart: (animation, value) => (animation.current = value),
onFrame: () => true,
current: 0,
animationIndex: 0,
reduceMotion: getReduceMotionForAnimation(reduceMotion),
} as SequenceAnimation;
});
}
return defineAnimation<SequenceAnimation>(
_animations[0] as SequenceAnimation,
() => {
'worklet';
const animations = _animations.map((a) => {
const result = typeof a === 'function' ? a() : a;
result.finished = false;
return result;
});
function findNextNonReducedMotionAnimationIndex(index: number) {
// the last animation is returned even if reduced motion is enabled,
// because we want the sequence to finish at the right spot
while (
index < animations.length - 1 &&
animations[index].reduceMotion
) {
index++;
}
return index;
}
const callback = (finished: boolean): void => {
if (finished) {
// we want to call the callback after every single animation
// not after all of them
return;
}
// this is going to be called only if sequence has been cancelled
animations.forEach((animation) => {
if (typeof animation.callback === 'function' && !animation.finished) {
animation.callback(finished);
}
});
};
function sequence(animation: SequenceAnimation, now: Timestamp): boolean {
const currentAnim = animations[animation.animationIndex];
const finished = currentAnim.onFrame(currentAnim, now);
animation.current = currentAnim.current;
if (finished) {
// we want to call the callback after every single animation
if (currentAnim.callback) {
currentAnim.callback(true /* finished */);
}
currentAnim.finished = true;
animation.animationIndex = findNextNonReducedMotionAnimationIndex(
animation.animationIndex + 1
);
if (animation.animationIndex < animations.length) {
const nextAnim = animations[animation.animationIndex];
nextAnim.onStart(nextAnim, currentAnim.current, now, currentAnim);
return false;
}
return true;
}
return false;
}
function onStart(
animation: SequenceAnimation,
value: AnimatableValue,
now: Timestamp,
previousAnimation: SequenceAnimation
): void {
// child animations inherit the setting, unless they already have it defined
// they will have it defined only if the user used the `reduceMotion` prop
animations.forEach((anim) => {
if (anim.reduceMotion === undefined) {
anim.reduceMotion = animation.reduceMotion;
}
});
animation.animationIndex = findNextNonReducedMotionAnimationIndex(0);
if (previousAnimation === undefined) {
previousAnimation = animations[
animations.length - 1
] as SequenceAnimation;
}
const currentAnimation = animations[animation.animationIndex];
currentAnimation.onStart(
currentAnimation,
value,
now,
previousAnimation
);
}
return {
isHigherOrder: true,
onFrame: sequence,
onStart,
animationIndex: 0,
current: animations[0].current,
callback,
reduceMotion: getReduceMotionForAnimation(reduceMotion),
} as SequenceAnimation;
}
);
}
;