UNPKG

5.55 kBPlain TextView Raw
1'use strict';
2import { defineAnimation, getReduceMotionForAnimation } from './util';
3import type { NextAnimation, SequenceAnimation } from './commonTypes';
4import type {
5 Animation,
6 AnimatableValue,
7 AnimationObject,
8 ReduceMotion,
9 Timestamp,
10} from '../commonTypes';
11
12/**
13 * Lets you run animations in a sequence.
14 *
15 * @param reduceMotion - Determines how the animation responds to the device's reduced motion accessibility setting. Default to `ReduceMotion.System` - {@link ReduceMotion}.
16 * @param animations - Any number of animation objects to be run in a sequence.
17 * @returns An [animation object](https://docs.swmansion.com/react-native-reanimated/docs/fundamentals/glossary#animation-object) which holds the current state of the animation/
18 * @see https://docs.swmansion.com/react-native-reanimated/docs/animations/withSequence
19 */
20export function withSequence<T extends AnimatableValue>(
21 _reduceMotion: ReduceMotion,
22 ...animations: T[]
23): T;
24
25export function withSequence<T extends AnimatableValue>(...animations: T[]): T;
26
27export function withSequence(
28 _reduceMotionOrFirstAnimation?: ReduceMotion | NextAnimation<AnimationObject>,
29 ..._animations: NextAnimation<AnimationObject>[]
30): Animation<SequenceAnimation> {
31 'worklet';
32 let reduceMotion: ReduceMotion | undefined;
33
34 // the first argument is either a config or an animation
35 // this is done to allow the reduce motion config prop to be optional
36 if (_reduceMotionOrFirstAnimation) {
37 if (typeof _reduceMotionOrFirstAnimation === 'string') {
38 reduceMotion = _reduceMotionOrFirstAnimation as ReduceMotion;
39 } else {
40 _animations.unshift(
41 _reduceMotionOrFirstAnimation as NextAnimation<AnimationObject>
42 );
43 }
44 }
45
46 if (_animations.length === 0) {
47 console.warn('[Reanimated] No animation was provided for the sequence');
48
49 return defineAnimation<SequenceAnimation>(0, () => {
50 'worklet';
51 return {
52 onStart: (animation, value) => (animation.current = value),
53 onFrame: () => true,
54 current: 0,
55 animationIndex: 0,
56 reduceMotion: getReduceMotionForAnimation(reduceMotion),
57 } as SequenceAnimation;
58 });
59 }
60
61 return defineAnimation<SequenceAnimation>(
62 _animations[0] as SequenceAnimation,
63 () => {
64 'worklet';
65
66 const animations = _animations.map((a) => {
67 const result = typeof a === 'function' ? a() : a;
68 result.finished = false;
69 return result;
70 });
71
72 function findNextNonReducedMotionAnimationIndex(index: number) {
73 // the last animation is returned even if reduced motion is enabled,
74 // because we want the sequence to finish at the right spot
75 while (
76 index < animations.length - 1 &&
77 animations[index].reduceMotion
78 ) {
79 index++;
80 }
81
82 return index;
83 }
84
85 const callback = (finished: boolean): void => {
86 if (finished) {
87 // we want to call the callback after every single animation
88 // not after all of them
89 return;
90 }
91 // this is going to be called only if sequence has been cancelled
92 animations.forEach((animation) => {
93 if (typeof animation.callback === 'function' && !animation.finished) {
94 animation.callback(finished);
95 }
96 });
97 };
98
99 function sequence(animation: SequenceAnimation, now: Timestamp): boolean {
100 const currentAnim = animations[animation.animationIndex];
101 const finished = currentAnim.onFrame(currentAnim, now);
102 animation.current = currentAnim.current;
103 if (finished) {
104 // we want to call the callback after every single animation
105 if (currentAnim.callback) {
106 currentAnim.callback(true /* finished */);
107 }
108 currentAnim.finished = true;
109 animation.animationIndex = findNextNonReducedMotionAnimationIndex(
110 animation.animationIndex + 1
111 );
112 if (animation.animationIndex < animations.length) {
113 const nextAnim = animations[animation.animationIndex];
114 nextAnim.onStart(nextAnim, currentAnim.current, now, currentAnim);
115 return false;
116 }
117 return true;
118 }
119 return false;
120 }
121
122 function onStart(
123 animation: SequenceAnimation,
124 value: AnimatableValue,
125 now: Timestamp,
126 previousAnimation: SequenceAnimation
127 ): void {
128 // child animations inherit the setting, unless they already have it defined
129 // they will have it defined only if the user used the `reduceMotion` prop
130 animations.forEach((anim) => {
131 if (anim.reduceMotion === undefined) {
132 anim.reduceMotion = animation.reduceMotion;
133 }
134 });
135 animation.animationIndex = findNextNonReducedMotionAnimationIndex(0);
136
137 if (previousAnimation === undefined) {
138 previousAnimation = animations[
139 animations.length - 1
140 ] as SequenceAnimation;
141 }
142
143 const currentAnimation = animations[animation.animationIndex];
144 currentAnimation.onStart(
145 currentAnimation,
146 value,
147 now,
148 previousAnimation
149 );
150 }
151
152 return {
153 isHigherOrder: true,
154 onFrame: sequence,
155 onStart,
156 animationIndex: 0,
157 current: animations[0].current,
158 callback,
159 reduceMotion: getReduceMotionForAnimation(reduceMotion),
160 } as SequenceAnimation;
161 }
162 );
163}
164
\No newline at end of file