UNPKG

react-native-reanimated

Version:

More powerful alternative to Animated library for React Native.

145 lines (133 loc) 4.73 kB
'use strict'; import type { AnimatableValue, Animation, AnimationCallback, AnimationObject, ReduceMotion, Timestamp, } from '../commonTypes'; import type { RepeatAnimation } from './commonTypes'; import { defineAnimation, getReduceMotionForAnimation } from './util'; // TODO TYPESCRIPT This is a temporary type to get rid of .d.ts file. type withRepeatType = <T extends AnimatableValue>( animation: T, numberOfReps?: number, reverse?: boolean, callback?: AnimationCallback, reduceMotion?: ReduceMotion ) => T; /** * Lets you repeat an animation given number of times or run it indefinitely. * * @param animation - An animation object you want to repeat. * @param numberOfReps - The number of times the animation is going to be * repeated. Defaults to 2. * @param reverse - Whether the animation should run in reverse every other * repetition. Defaults to false. * @param callback - A function called on animation complete. * @param reduceMotion - Determines how the animation responds to the device's * reduced motion accessibility setting. Default to `ReduceMotion.System` - * {@link ReduceMotion}. * @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/withRepeat */ export const withRepeat = function <T extends AnimationObject>( _nextAnimation: T | (() => T), numberOfReps = 2, reverse = false, callback?: AnimationCallback, reduceMotion?: ReduceMotion ): Animation<RepeatAnimation> { 'worklet'; return defineAnimation<RepeatAnimation, T>( _nextAnimation, (): RepeatAnimation => { 'worklet'; const nextAnimation = typeof _nextAnimation === 'function' ? _nextAnimation() : _nextAnimation; function repeat(animation: RepeatAnimation, now: Timestamp): boolean { const finished = nextAnimation.onFrame(nextAnimation, now); animation.current = nextAnimation.current; if (finished) { animation.reps += 1; // call inner animation's callback on every repetition // as the second argument the animation's current value is passed if (nextAnimation.callback) { nextAnimation.callback(true /* finished */, animation.current); } if ( animation.reduceMotion || (numberOfReps > 0 && animation.reps >= numberOfReps) ) { return true; } const startValue = reverse ? (nextAnimation.current as number) : animation.startValue; if (reverse) { nextAnimation.toValue = animation.startValue; animation.startValue = startValue; } nextAnimation.onStart( nextAnimation, startValue, now, nextAnimation.previousAnimation as RepeatAnimation ); return false; } return false; } const repCallback = (finished?: boolean): void => { if (callback) { callback(finished); } // when cancelled call inner animation's callback if (!finished && nextAnimation.callback) { nextAnimation.callback(false /* finished */); } }; function onStart( animation: RepeatAnimation, value: AnimatableValue, now: Timestamp, previousAnimation: Animation<any> | null ): void { animation.startValue = value; animation.reps = 0; // child animations inherit the setting, unless they already have it defined // they will have it defined only if the user used the `reduceMotion` prop if (nextAnimation.reduceMotion === undefined) { nextAnimation.reduceMotion = animation.reduceMotion; } // don't start the animation if reduced motion is enabled and // the animation would end at its starting point if ( animation.reduceMotion && reverse && (numberOfReps <= 0 || numberOfReps % 2 === 0) ) { animation.current = animation.startValue; animation.onFrame = () => true; } else { nextAnimation.onStart(nextAnimation, value, now, previousAnimation); } } return { isHigherOrder: true, onFrame: repeat, onStart, reps: 0, current: nextAnimation.current, callback: repCallback, startValue: 0, reduceMotion: getReduceMotionForAnimation(reduceMotion), }; } ); } as withRepeatType;