Skip to content

Commit

Permalink
Introducing Keyframe-like animation definition schema (#2195)
Browse files Browse the repository at this point in the history
To simplify the way how complex animations are defined, this PR introduces a keyframe like animation definition schema. It is inspired by the animation definition schema used in CSS and react-native-animatable.
  • Loading branch information
jmysliv committed Jul 20, 2021
1 parent 0c2f66f commit 84558ff
Show file tree
Hide file tree
Showing 10 changed files with 553 additions and 3 deletions.
5 changes: 5 additions & 0 deletions Example/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import AnimatedTabBarExample from './AnimatedTabBarExample';
import LightboxExample from './LightboxExample';
import LiquidSwipe from './LiquidSwipe';
import ScrollExample from './AnimatedScrollExample';
import { KeyframeAnimation } from './LayoutReanimation/KeyframeAnimation';
LogBox.ignoreLogs(['Calling `getNode()`']);

type Screens = Record<string, { screen: React.ComponentType; title?: string }>;
Expand All @@ -42,6 +43,10 @@ const SCREENS: Screens = {
screen: DefaultAnimations,
title: '🆕 Default layout animations',
},
KeyframeAnimation: {
screen: KeyframeAnimation,
title: '🆕 Keyframe animation',
},
CustomLayoutAnimation: {
screen: CustomLayoutAnimationScreen,
title: '🆕 Custom layout animation',
Expand Down
70 changes: 70 additions & 0 deletions Example/src/LayoutReanimation/KeyframeAnimation.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import React, { useState } from 'react';
import { View, Button } from 'react-native';
import Animated, {
AnimatedLayout,
Easing,
Keyframe,
} from 'react-native-reanimated';

export function KeyframeAnimation(): React.ReactElement {
const [show, setShow] = useState(false);
const enteringAnimation = new Keyframe({
from: {
originX: 50,
transform: [{ rotate: '45deg' }, { scale: 0.5 }],
},
30: {
transform: [{ rotate: '-90deg' }, { scale: 2 }],
},
50: {
originX: 70,
},
100: {
originX: 0,
transform: [{ rotate: '0deg' }, { scale: 1 }],
easing: Easing.quad,
},
}).duration(2000);
const exitingAnimation = new Keyframe({
0: {
opacity: 1,
transform: [{ skewX: '0deg' }],
},
30: {
transform: [{ skewX: '40deg' }],
easing: Easing.exp,
},
to: {
opacity: 0,
transform: [{ skewX: '-10deg' }],
},
}).duration(2000);
return (
<View style={{ flexDirection: 'column-reverse' }}>
<Button
title="animate"
onPress={() => {
setShow((last) => !last);
}}
/>
<View
style={{ height: 400, alignItems: 'center', justifyContent: 'center' }}>
{show && (
<AnimatedLayout>
<Animated.View
entering={enteringAnimation}
exiting={exitingAnimation}
style={{
height: 100,
width: 200,
backgroundColor: 'blue',
alignItems: 'center',
justifyContent: 'center',
}}
/>
</AnimatedLayout>
)}
</View>
</View>
);
}
2 changes: 2 additions & 0 deletions docs/docs/api/LayoutAnimations/EntryAnimations.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ In React Native every component appears instantly whenever you add it to the com

We provide an easy API that allows you to code almost any animation you want. Because some of the animations are more frequently used than the others we coded them for you and provided in an accessible way. Below you can find an instruction step by step explaining how to use them. A little further down you will find a detailed description of all the predefined entering animations.

If you want to create more complex animation you can use [Keyframes](keyframeAnimations).

## How to use predefined entering animation?

### 1. Import chosen animation
Expand Down
3 changes: 2 additions & 1 deletion docs/docs/api/LayoutAnimations/ExitAnimations.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ sidebar_label: Exiting Animations

In React Native during unmounting of components from the hierarchy of views, it just disappears in the next frame. However you can beautify this process using `Exiting Animations`. Reanimated make a pretty animation of disappearing of component for you.
#### How it is possible?
Reanimated listen on changes in tree of views and if detect that some of component should disappear in next frame, It replaces this process with exiting animation. It is easy and fast. You can use predefined animations - examples below or you can define your own custom animation.
Reanimated listen on changes in tree of views and if detect that some of component should disappear in next frame, It replaces this process with exiting animation. It is easy and fast. You can use predefined animations - examples below or you can define your own custom animation. If you want to create more complex animation you can use [Keyframes](keyframeAnimations).


## How to use predefined exiting animation?

Expand Down
199 changes: 199 additions & 0 deletions docs/docs/api/LayoutAnimations/KeyframeAnimations.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,199 @@
---
id: keyframeAnimations
title: Keyframe Animations
sidebar_label: Keyframe Animations
---

The document explains how you can define complex animation using simple and popular animation definitions schema - Keyframes.

## How to define Keyframe animation?

### 1. Import Keyframe

```js
import { Keyframe } from 'react-native-reanimated';
```

### 2. Create Keyframe object, define initial and final state

In Keyframe's constructor pass object with definitions of your animation. Object keys should be within range `0-100` and correspond to animation progress,
so to `0` assign the style, you want for your object at the beginning of the animation and to `100` assign the style you want for your object to have at the end of the animation.

```js
import { Keyframe } from 'react-native-reanimated';

const keyframe = new Keyframe({
0: {
transform: [{ rotate: '0deg' }],
},
100: {
transform: [{ rotate: '45deg' }],
},
}
```
Instead of using `0` and `100`, you can define edge points using `from` and `to` keywords. The result will be the same.
```js
import { Keyframe } from 'react-native-reanimated';

const keyframe = new Keyframe({
from: {
transform: [{ rotate: '0deg' }],
},
to: {
transform: [{ rotate: '45deg' }],
},
}
```
Providing keyframe `0` or `from` is required as it contains the initial state of the object you want to animate.
Make sure you provided the initial value for all style properties you want to animate in other keyframes.
Remember not to provide both `0` and `from`, or `100` and `to` keyframe as it will result in parsing conflict.
### 3. Add middle points
Between edge points, you can define middle points in which you want your object to have certain style properties.
Remember that you can specify style only for those properties that you set the initial value in `0` or `from` keyframe.
If you want to animate transform style, make sure that all properties in the transformation array are in the same order in all keyframes.
```js
import { Keyframe } from 'react-native-reanimated';

const keyframe = new Keyframe({
0: {
transform: [{ rotate: '0deg' }],
},
45: {
transform: [{ rotate: '100deg' }]
},
100: {
transform: [{ rotate: '45deg' }],
},
}
```
### 4. Customize transitions using an easing function
If easing property is not provided, it defaults to linear easing function.
```js
import { Keyframe, Easing } from 'react-native-reanimated';

const keyframe = new Keyframe({
0: {
transform: [{ rotate: '0deg' }],
},
45: {
transform: [{ rotate: '100deg' }],
easing: Easing.exp,
},
100: {
transform: [{ rotate: '45deg' }],
},
}
```
## How to use keyframe animations?
Currently, you can define animations using keyframes only for entry and exit animations.
### 1. Choose Animated Component which entering or exiting you want to animate
```js
// AnimatedComponent - component created by createAnimatedComponent or imported from Reanimated
// keyframe - Keyframe object
<AnimatedComponent exiting={keyframe} />
```
### 2. Customize the animation
```js
<AnimatedComponent exiting={keyframe.duration(3000).delay(200)} />
```
### 3. Make sure that your animated component is under an AnimatedLayout. If it's not then add AnimatedLayout somewhere above the component.
```js
<AnimatedLayout> // +
<Text> sth </Text>
<AnimatedComponent exiting={keyframe.duration(3000).delay(200)} />
</AnimatedLayout> // +
```
## Available modifiers
The order of modifiers doesn't matter.
### duration
default: 500
How long the animation should last.
### delay
default: 0
Allows to start with a specified delay.
## Example
```js
export function KeyframeAnimation(): React.ReactElement {
const [show, setShow] = useState(false);

const enteringAnimation = new Keyframe({
0: {
originX: 50,
transform: [{ rotate: '45deg' }],
},
30: {
originX: 10,
transform: [{ rotate: '-90deg' }],
},
100: {
originX: 0,
transform: [{ rotate: '0deg' }],
easing: Easing.quad,
},
}).duration(2000);

const exitingAnimation = new Keyframe({
0: {
opacity: 1,
transform: [{ skewX: '0deg' }],
},
30: {
opacity: 0.5,
transform: [{ skewX: '40deg' }],
easing: Easing.exp,
},
100: {
opacity: 0,
transform: [{ skewX: '-10deg' }],
},
}).duration(2000);

return (
<View style={{ flexDirection: 'column-reverse' }}>
<Button
title="animate"
onPress={() => {
setShow((last) => !last);
}}
/>
<View
style={{ height: 400, alignItems: 'center', justifyContent: 'center' }}>
{show && (
<AnimatedLayout>
<Animated.View
entering={enteringAnimation}
exiting={exitingAnimation}
style={{
height: 100,
width: 200,
backgroundColor: 'blue',
alignItems: 'center',
justifyContent: 'center',
}}
/>
</AnimatedLayout>
)}
</View>
</View>
);
}
```
<video src="https://user-images.githubusercontent.com/48885911/125463255-04502655-3147-4d15-ae5b-f327666eadff.mov" controls="controls" muted="muted"></video>
1 change: 1 addition & 0 deletions docs/sidebars.js
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ module.exports = {
'api/LayoutAnimations/customAnimations',
'api/LayoutAnimations/entryAnimations',
'api/LayoutAnimations/exitAnimations',
'api/LayoutAnimations/keyframeAnimations',
'api/LayoutAnimations/layoutTransitions',
],
Miscellaneous: ['api/runOnJS'],
Expand Down
16 changes: 14 additions & 2 deletions react-native-reanimated.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -259,8 +259,8 @@ declare module 'react-native-reanimated' {
} & {
animatedProps?: Partial<AnimateProps<P>>;
layout?: Layout | LayoutAnimationFunction;
entering?: BaseAnimationBuilder | ZoomRotateAnimationBuilder | BounceAnimationBuilder | EntryExitAnimationFunction;
exiting?: BaseAnimationBuilder | ZoomRotateAnimationBuilder | BounceAnimationBuilder | EntryExitAnimationFunction;
entering?: BaseAnimationBuilder | ZoomRotateAnimationBuilder | BounceAnimationBuilder | EntryExitAnimationFunction | Keyframe;
exiting?: BaseAnimationBuilder | ZoomRotateAnimationBuilder | BounceAnimationBuilder | EntryExitAnimationFunction | Keyframe;
};

type CodeProps = {
Expand Down Expand Up @@ -636,6 +636,17 @@ declare module 'react-native-reanimated' {
}): void;
export function addWhitelistedUIProps(props: { [key: string]: true }): void;

export type StyleProps =
| ViewStyle
| TextStyle
| { originX?: number; originY?: number };

export type KeyframeProps = StyleProps | { easing?: EasingFn };
export class Keyframe {
constructor(definitions: Map<number, KeyframeProps[]>);
duration(durationMs: number): Keyframe;
delay(delayMs: number): Keyframe;
}
export class BaseAnimationBuilder {
static duration(durationMs: number): BaseAnimationBuilder;
duration(durationMs: number): BaseAnimationBuilder;
Expand Down Expand Up @@ -1016,4 +1027,5 @@ declare module 'react-native-reanimated' {
export const RollInRight: typeof Animated.RollInRight;
export const RollOutLeft: typeof Animated.RollOutLeft;
export const RollOutRight: typeof Animated.RollOutRight;
export const Keyframe: Keyframe;
}

0 comments on commit 84558ff

Please sign in to comment.