UNPKG

4.7 kBPlain TextView Raw
1'use strict';
2import { defineAnimation, getReduceMotionForAnimation } from './util';
3import type {
4 Animation,
5 AnimationCallback,
6 AnimatableValue,
7 Timestamp,
8 AnimationObject,
9 ReduceMotion,
10} from '../commonTypes';
11import type { RepeatAnimation } from './commonTypes';
12
13// TODO TYPESCRIPT This is a temporary type to get rid of .d.ts file.
14type withRepeatType = <T extends AnimatableValue>(
15 animation: T,
16 numberOfReps?: number,
17 reverse?: boolean,
18 callback?: AnimationCallback,
19 reduceMotion?: ReduceMotion
20) => T;
21
22/**
23 * Lets you repeat an animation given number of times or run it indefinitely.
24 *
25 * @param animation - An animation object you want to repeat.
26 * @param numberOfReps - The number of times the animation is going to be repeated. Defaults to 2.
27 * @param reverse - Whether the animation should run in reverse every other repetition. Defaults to false.
28 * @param callback - A function called on animation complete.
29 * @param reduceMotion - Determines how the animation responds to the device's reduced motion accessibility setting. Default to `ReduceMotion.System` - {@link ReduceMotion}.
30 * @returns An [animation object](https://docs.swmansion.com/react-native-reanimated/docs/fundamentals/glossary#animation-object) which holds the current state of the animation.
31 * @see https://docs.swmansion.com/react-native-reanimated/docs/animations/withRepeat
32 */
33export const withRepeat = function <T extends AnimationObject>(
34 _nextAnimation: T | (() => T),
35 numberOfReps = 2,
36 reverse = false,
37 callback?: AnimationCallback,
38 reduceMotion?: ReduceMotion
39): Animation<RepeatAnimation> {
40 'worklet';
41
42 return defineAnimation<RepeatAnimation, T>(
43 _nextAnimation,
44 (): RepeatAnimation => {
45 'worklet';
46
47 const nextAnimation =
48 typeof _nextAnimation === 'function'
49 ? _nextAnimation()
50 : _nextAnimation;
51
52 function repeat(animation: RepeatAnimation, now: Timestamp): boolean {
53 const finished = nextAnimation.onFrame(nextAnimation, now);
54 animation.current = nextAnimation.current;
55 if (finished) {
56 animation.reps += 1;
57 // call inner animation's callback on every repetition
58 // as the second argument the animation's current value is passed
59 if (nextAnimation.callback) {
60 nextAnimation.callback(true /* finished */, animation.current);
61 }
62 if (
63 animation.reduceMotion ||
64 (numberOfReps > 0 && animation.reps >= numberOfReps)
65 ) {
66 return true;
67 }
68
69 const startValue = reverse
70 ? (nextAnimation.current as number)
71 : animation.startValue;
72 if (reverse) {
73 nextAnimation.toValue = animation.startValue;
74 animation.startValue = startValue;
75 }
76 nextAnimation.onStart(
77 nextAnimation,
78 startValue,
79 now,
80 nextAnimation.previousAnimation as RepeatAnimation
81 );
82 return false;
83 }
84 return false;
85 }
86
87 const repCallback = (finished?: boolean): void => {
88 if (callback) {
89 callback(finished);
90 }
91 // when cancelled call inner animation's callback
92 if (!finished && nextAnimation.callback) {
93 nextAnimation.callback(false /* finished */);
94 }
95 };
96
97 function onStart(
98 animation: RepeatAnimation,
99 value: AnimatableValue,
100 now: Timestamp,
101 previousAnimation: Animation<any> | null
102 ): void {
103 animation.startValue = value;
104 animation.reps = 0;
105
106 // child animations inherit the setting, unless they already have it defined
107 // they will have it defined only if the user used the `reduceMotion` prop
108 if (nextAnimation.reduceMotion === undefined) {
109 nextAnimation.reduceMotion = animation.reduceMotion;
110 }
111
112 // don't start the animation if reduced motion is enabled and
113 // the animation would end at its starting point
114 if (
115 animation.reduceMotion &&
116 reverse &&
117 (numberOfReps <= 0 || numberOfReps % 2 === 0)
118 ) {
119 animation.current = animation.startValue;
120 animation.onFrame = () => true;
121 } else {
122 nextAnimation.onStart(nextAnimation, value, now, previousAnimation);
123 }
124 }
125
126 return {
127 isHigherOrder: true,
128 onFrame: repeat,
129 onStart,
130 reps: 0,
131 current: nextAnimation.current,
132 callback: repCallback,
133 startValue: 0,
134 reduceMotion: getReduceMotionForAnimation(reduceMotion),
135 };
136 }
137 );
138} as withRepeatType;