Skip to content

Commit

Permalink
feat(🐎): Update higher order functions to work with Reanimated 2.8 (#491
Browse files Browse the repository at this point in the history
)
  • Loading branch information
wcandillon committed May 24, 2022
1 parent a7d7e88 commit 14875d1
Show file tree
Hide file tree
Showing 4 changed files with 125 additions and 179 deletions.
2 changes: 1 addition & 1 deletion package.json
Expand Up @@ -42,7 +42,7 @@
"react": "^16.8.6",
"react-native": "^0.61.0",
"react-native-gesture-handler": "~1.5.0",
"react-native-reanimated": "2.1.0",
"react-native-reanimated": "~2.8.0",
"semantic-release": "^15.13.3",
"semantic-release-cli": "^4.1.2",
"typescript": "4.3.5"
Expand Down
140 changes: 42 additions & 98 deletions src/Animations.ts
@@ -1,102 +1,22 @@
import type Animated from "react-native-reanimated";
/* eslint-disable @typescript-eslint/consistent-type-imports */
import type { AnimatableValue, Animation } from "react-native-reanimated";
import Animated, { defineAnimation } from "react-native-reanimated";

declare let _WORKLET: boolean;

export interface AnimationState {
current: number;
}

export interface PhysicsAnimationState extends AnimationState {
velocity: number;
}

export type Animation<
State extends AnimationState = AnimationState,
PrevState = State
> = {
onFrame: (animation: State, now: number) => boolean;
onStart: (
animation: State,
value: number,
now: number,
lastAnimation: PrevState
) => void;
callback?: () => void;
} & State;

export type AnimationParameter<State extends AnimationState = AnimationState> =
| Animation<State>
| (() => Animation<State>)
| number;

/**
* @summary Access animations passed as parameters safely on both the UI and JS thread with the proper static types.
* Animations can receive other animations as parameter.
*/
export const animationParameter = <
State extends AnimationState = AnimationState
>(
animationParam: AnimationParameter<State>
) => {
"worklet";
if (typeof animationParam === "number") {
throw new Error("Expected Animation as parameter");
}
return typeof animationParam === "function"
? animationParam()
: animationParam;
};

/**
* @summary Declare custom animations that can be invoked on both the JS and UI thread.
* @example
* defineAnimation(() => {
"worklet";
// ...animation code
return {
animation,
start
}
});
* @worklet
*/
export const defineAnimation = <
S extends AnimationState = AnimationState,
Prev extends AnimationState = AnimationState
>(
factory: () => Omit<Animation<S, Prev>, keyof S>
) => {
"worklet";
if (_WORKLET) {
return factory() as unknown as number;
}
return factory as unknown as number;
};

interface PausableAnimation extends AnimationState {
interface PausableAnimation extends Animation<PausableAnimation> {
lastTimestamp: number;
elapsed: number;
}

/**
* @summary Make an animation pausable. The state of the animation (paused or not)
* is controlled by a boolean shared value.
* @example
const progress = useSharedValue(0);
const paused = useSharedValue(false);
useEffect(() => {
progress.value = withPause(withLoop(withTiming(1)), paused);
}, []);
* @worklet
*/
export const withPause = (
animationParam: AnimationParameter,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
_nextAnimation: any,
paused: Animated.SharedValue<boolean>
) => {
"worklet";
return defineAnimation<PausableAnimation>(() => {
return defineAnimation<PausableAnimation>(_nextAnimation, () => {
"worklet";
const nextAnimation = animationParameter(animationParam);
const nextAnimation: PausableAnimation =
typeof _nextAnimation === "function" ? _nextAnimation() : _nextAnimation;
const onFrame = (state: PausableAnimation, now: number) => {
const { lastTimestamp, elapsed } = state;
if (paused.value) {
Expand All @@ -111,22 +31,40 @@ export const withPause = (
};
const onStart = (
state: PausableAnimation,
value: number,
value: AnimatableValue,
now: number,
previousState: AnimationState
previousState: PausableAnimation
) => {
state.lastTimestamp = now;
state.elapsed = 0;
state.current = 0;
nextAnimation.onStart(nextAnimation, value, now, previousState);
};
const callback = (finished?: boolean): void => {
if (nextAnimation.callback) {
nextAnimation.callback(finished);
}
};
return {
onFrame,
onStart,
callback: nextAnimation.callback,
isHigherOrder: true,
current: nextAnimation.current,
callback,
previousAnimation: null,
startTime: 0,
started: false,
lastTimestamp: 0,
elapsed: 0,
};
});
};

export interface PhysicsAnimation extends Animation<PhysicsAnimation> {
velocity: number;
current: number;
}

/**
* @summary Add a bouncing behavior to a physics-based animation.
* An animation is defined as being physics-based if it contains a velocity in its state.
Expand All @@ -136,15 +74,19 @@ export const withPause = (
* @worklet
*/
export const withBouncing = (
animationParam: AnimationParameter<PhysicsAnimationState>,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
_nextAnimation: any,
lowerBound: number,
upperBound: number
): number => {
"worklet";
return defineAnimation<PhysicsAnimationState, PhysicsAnimationState>(() => {
return defineAnimation<PhysicsAnimation>(_nextAnimation, () => {
"worklet";
const nextAnimation = animationParameter(animationParam);
const onFrame = (state: PhysicsAnimationState, now: number) => {

const nextAnimation: PhysicsAnimation =
typeof _nextAnimation === "function" ? _nextAnimation() : _nextAnimation;

const onFrame = (state: PhysicsAnimation, now: number) => {
const finished = nextAnimation.onFrame(nextAnimation, now);
const { velocity, current } = nextAnimation;
state.current = current;
Expand All @@ -158,17 +100,19 @@ export const withBouncing = (
return finished;
};
const onStart = (
_state: PhysicsAnimationState,
_state: PhysicsAnimation,
value: number,
now: number,
previousState: PhysicsAnimationState
previousState: PhysicsAnimation
) => {
nextAnimation.onStart(nextAnimation, value, now, previousState);
};
return {
onFrame,
onStart,
current: nextAnimation.current,
callback: nextAnimation.callback,
velocity: 0,
};
});
};
9 changes: 6 additions & 3 deletions src/Transitions.ts
@@ -1,5 +1,8 @@
import { useEffect } from "react";
import type Animated from "react-native-reanimated";
import type {
WithSpringConfig,
WithTimingConfig,
} from "react-native-reanimated";
import {
useSharedValue,
useDerivedValue,
Expand All @@ -11,7 +14,7 @@ import { bin } from "./Math";

export const useSpring = (
state: boolean | number,
config?: Animated.WithSpringConfig
config?: WithSpringConfig
) => {
const value = useSharedValue(0);
useEffect(() => {
Expand All @@ -25,7 +28,7 @@ export const useSpring = (

export const useTiming = (
state: boolean | number,
config?: Animated.WithTimingConfig
config?: WithTimingConfig
) => {
const value = useSharedValue(0);
useEffect(() => {
Expand Down

0 comments on commit 14875d1

Please sign in to comment.