Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,16 @@ You can also animate imperatively by using the `animate()` function on the eleme
this.view.animate({ 0: { opacity: 0 }, 1: { opacity: 1 } });
```

The `animate()` method accepts two extra optional arguments after the animation definition:

* **`duration`** – overrides the default duration in milliseconds.
* **`iterationDelay`** – pause between iterations in milliseconds, useful for chained loops.

```js
// Run a custom animation for 600ms with a 200ms pause between each of 4 iterations.
this.view.animate({ 0: { opacity: 0 }, 1: { opacity: 1 } }, 600, 200);
```

#### Generic transitions

##### `transition(fromValues, toValues[[, duration], easing])`
Expand Down
13 changes: 12 additions & 1 deletion createAnimatableComponent.js
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ function getCompiledAnimation(animation) {
if (typeof animation === 'string') {
const compiledAnimation = getAnimationByName(animation);
if (!compiledAnimation) {
throw new Error(`No animation registred by the name of ${animation}`);
throw new Error(`No animation registered by the name of ${animation}`);
}
return compiledAnimation;
}
Expand Down Expand Up @@ -123,6 +123,17 @@ function transitionToValue(
}

// Make (almost) any component animatable, similar to Animated.createAnimatedComponent
/**
* Higher-order component factory that makes any React Native
* component animatable. The returned class component mirrors the
* `Animated.createAnimatedComponent` API but also accepts the full
* set of `AnimatableProps` and exposes every registered animation
* as an imperative method on its ref.
*
* @param {React.ComponentType} WrappedComponent - Component to wrap.
* @returns {React.ComponentClass} An animatable component class
* with `displayName` of the form `withAnimatable(<original>)`.
*/
export default function createAnimatableComponent(WrappedComponent) {
const wrappedComponentName =
WrappedComponent.displayName || WrappedComponent.name || 'Component';
Expand Down
12 changes: 12 additions & 0 deletions createAnimation.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,18 @@ function parsePosition(value) {

const cache = {};

/**
* Compile a {@link CustomAnimation} definition into a structure
* suitable for `Animated.Value.interpolate()`. The compiled output
* is cached on a stringified-definition key, so calling this with
* the same definition repeatedly is cheap.
*
* @param {object} definition - The custom animation to compile.
* Keyframes may be keyed by `'from'`, `'to'`, or any number in
* the `[0, 1]` range.
* @returns {object} The compiled animation, keyed by style property.
* @throws {Error} If fewer than two valid keyframes are present.
*/
export default function createAnimation(definition) {
const cacheKey = JSON.stringify(definition);
if (cache[cacheKey]) {
Expand Down
11 changes: 11 additions & 0 deletions index.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
/**
* @file Public entry point for `react-native-animatable`.
*
* The library automatically registers all built-in animation
* definitions exported from `./definitions` on module load, so the
* names listed in the `Animation` type are usable immediately.
*/
import {
View as CoreView,
Text as CoreText,
Expand All @@ -9,9 +16,13 @@ import * as ANIMATION_DEFINITIONS from './definitions';

initializeRegistryWithDefinitions(ANIMATION_DEFINITIONS);

/** Compose any React component into an animatable component. */
export const createAnimatableComponent = createComponent;
/** Precomposed animatable `View`. */
export const View = createComponent(CoreView);
/** Precomposed animatable `Text`. */
export const Text = createComponent(CoreText);
/** Precomposed animatable `Image`. */
export const Image = createComponent(CoreImage);
export { default as createAnimation } from './createAnimation';
export {
Expand Down
30 changes: 30 additions & 0 deletions registry.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,48 @@ import createAnimation from './createAnimation';

const animationRegistry = {};

/**
* Register a single custom animation under the given name so it can
* later be referenced by string from the `animation` prop.
*
* @param {string} animationName - The lookup key for the animation.
* @param {object} animation - A {@link CustomAnimation} definition
* that will be compiled via `createAnimation` before being stored.
*/
export function registerAnimation(animationName, animation) {
animationRegistry[animationName] = animation;
}
Comment on lines 13 to 15

/**
* Look up a previously registered animation by name. Returns
* `undefined` if no animation has been registered under that name.
*
* @param {string} animationName - The name passed to `registerAnimation`.
* @returns {object|undefined} The compiled animation, or `undefined`.
*/
export function getAnimationByName(animationName) {
return animationRegistry[animationName];
}

/**
* Return the list of all animation names currently registered, both
* the built-in ones and any user-registered custom animations.
*
* @returns {string[]} Array of animation names.
*/
export function getAnimationNames() {
return Object.keys(animationRegistry);
}

/**
* Bulk-register a map of animation definitions. Each value is
* compiled with `createAnimation` and stored under its key. Existing
* animations with the same name will be replaced, which is useful
* for tweaking built-in animations.
*
* @param {{[key: string]: object}} definitions - Map of name to
* {@link CustomAnimation} definition.
*/
export function initializeRegistryWithDefinitions(definitions) {
Object.keys(definitions).forEach((animationName) => {
registerAnimation(
Expand Down
102 changes: 98 additions & 4 deletions typings/react-native-animatable.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,18 @@ import {
Component,
} from 'react';

/**
* A function that maps animation progress (`t` in `[0, 1]`) to a
* transformed progress value, mirroring the shape of React Native's
* `Easing` functions.
*/
export type EasingFunction = { (t: number): number };

/**
* The set of supported named easing functions plus any custom
* `EasingFunction`. Use the strings for the built-in easings shipped
* with this library, or supply a function for full control.
*/
export type Easing =
| 'linear'
| 'ease'
Expand Down Expand Up @@ -47,6 +58,11 @@ export type Easing =
| 'ease-in-out-back'
| EasingFunction;

/**
* All built-in animation names shipped with `react-native-animatable`.
* Custom animations registered via `registerAnimation` may be passed
* using the `string` overload of `AnimatableProps.animation`.
*/
export type Animation =
| 'bounce'
| 'flash'
Expand Down Expand Up @@ -111,12 +127,18 @@ export type Animation =
| 'zoomOutLeft'
| 'zoomOutRight';

/**
* Direction an animation plays. `alternate` and `alternate-reverse`
* are most useful with `iterationCount` greater than one, producing
* a smooth ping-pong effect.
*/
export type Direction =
| 'normal'
| 'reverse'
| 'alternate'
| 'alternate-reverse';

/** Style transform keys that may be used in a `transition` prop. */
type TransformKeys =
| 'perspective'
| 'rotate'
Expand All @@ -132,29 +154,70 @@ type TransformKeys =
| 'skewY'
| 'matrix';

/**
* Result passed to the `onAnimationEnd` / animation `Promise`
* resolution. `finished` is `false` when the animation was
* cancelled (for example by `stopAnimation()` or unmounting).
*/
export interface EndState {
finished: boolean;
}

/**
* Props accepted by every component created with
* `createAnimatableComponent`. These are passed to the underlying
* component in addition to its own props.
*/
interface AnimatableProps<S extends {}> {
/**
* Name of the animation, an inline {@link CustomAnimation}
* definition, or a previously registered custom animation name.
*/
animation?: Animation | string | CustomAnimation;
/** Animation duration in milliseconds. */
duration?: number;
/** Delay before the animation starts, in milliseconds. */
delay?: number;
/** Direction the animation plays. Defaults to `normal`. */
direction?: Direction;
/** Timing function used for the animation. */
easing?: Easing;
/** Number of iterations or the string `'infinite'`. */
iterationCount?: number | 'infinite';
/** Delay between iterations, in milliseconds. */
iterationDelay?: number;
/**
* Style property (or array of properties) to transition between
* declarative style changes.
*/
transition?:
| (keyof S | TransformKeys)
| ReadonlyArray<keyof S | TransformKeys>;
/** Whether to use the native animation driver. */
useNativeDriver?: boolean;
/**
* Whether the animation should create an "interaction handle" on
* React Native's `InteractionManager`. When `undefined` the
* component defaults to `true` for non-looping animations and
* `false` for looping ones.
*/
isInteraction?: boolean;
onAnimationBegin?: Function;
onAnimationEnd?: Function;
/** Called when the animation begins, after any configured `delay`. */
onAnimationBegin?: () => void;
/** Called when the animation ends, whether finished or cancelled. */
onAnimationEnd?: (endState: EndState) => void;
/** Called when a transition for `property` begins. */
onTransitionBegin?: (property: string) => void;
/** Called when a transition for `property` ends. */
onTransitionEnd?: (property: string) => void;
}

/** Per-animation imperative methods exposed on the component ref. */
type AnimatableAnimationMethods = Partial<{
[k in Animation]: (duration?: number) => Promise<{ finished: boolean }>;
[k in Animation]: (duration?: number) => Promise<EndState>;
}>;

/** Public shape of an animatable component. */
interface AnimatableComponent<P extends {}, S extends {}>
extends NativeMethods,
AnimatableAnimationMethods,
Expand All @@ -164,28 +227,41 @@ interface AnimatableComponent<P extends {}, S extends {}>
[key: string]: Component<P, S>;
};

/** Stop the current animation and clear the animation style. */
stopAnimation(): void;

/**
* Imperatively run a custom animation. The returned promise
* resolves with `endState` describing whether the animation
* finished or was cancelled.
*/
animate(
animation: Animation | CustomAnimation,
duration?: number,
iterationDelay?: number,
): Promise<void>;
): Promise<EndState>;

/** Transition between two full style snapshots. */
transition<T extends S>(
fromValues: T,
toValues: T,
duration?: number,
easing?: Easing,
): void;

/** Transition to a new style snapshot, inferring the start state. */
transitionTo<T extends S>(
toValues: T,
duration?: number,
easing?: Easing,
): void;
}

/**
* User-defined animation description. Keyframes may use numeric
* positions between `0` and `1`, or the convenience keys `from`
* (equivalent to `0`) and `to` (equivalent to `1`).
*/
export interface CustomAnimation<T = TextStyle & ViewStyle & ImageStyle> {
from?: T;
to?: T;
Expand All @@ -194,18 +270,36 @@ export interface CustomAnimation<T = TextStyle & ViewStyle & ImageStyle> {
[progress: number]: T;
}

/**
* Compile a {@link CustomAnimation} into a structure suitable for
* `Animated.Value.interpolate()`. Cached internally by stringified
* definition for performance.
*/
export function createAnimation(animation: CustomAnimation): object;

/** Register a custom animation under `name` for declarative lookup. */
export function registerAnimation(
name: string,
animation: CustomAnimation,
): void;

/**
* Bulk-register a map of animations, replacing any previously
* registered definitions with the same name. Useful for tweaking
* built-in animations.
*/
export function initializeRegistryWithDefinitions(animations: {
[key: string]: CustomAnimation;
}): void;

type GetPropertyType<B, K extends keyof B> = B[K];

/**
* Compose any component into an animatable component. The returned
* component exposes all props of `Component` plus the
* `AnimatableProps` listed above, and all built-in animation
* methods on its ref.
*/
export function createAnimatableComponent<
P extends { style?: any },
S = GetPropertyType<P, 'style'>,
Expand Down