Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

docs: Rewrite Keyframe Animations documentation page #6181

Merged
merged 15 commits into from
Jul 18, 2024
Merged
228 changes: 59 additions & 169 deletions packages/docs-reanimated/docs/layout-animations/keyframe-animations.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -2,66 +2,16 @@
sidebar_position: 2
---

# Keyframe animations

import DocsCompatibilityInfo from '../_shared/_docs_compatibility_info.mdx';

<DocsCompatibilityInfo />

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';
import Keyframe from '@site/src/examples/KeyframeAnimation';
import KeyframeSrc from '!!raw-loader!@site/src/examples/KeyframeAnimation';

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.
# Keyframe animations

### 3. Add middle points
Keyframes are animation definition schemas that let you create complex animations. They allow you more flexibility than standard Entering and Exiting presets.

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.
## Reference

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

const keyframe = new Keyframe({
Expand All @@ -70,143 +20,83 @@ const keyframe = new Keyframe({
},
45: {
transform: [{ rotate: '100deg' }],
easing: Easing.exp,
},
100: {
transform: [{ rotate: '45deg' }],
},
});
```

### 4. Customize transitions using an easing function

If easing property is not provided, it defaults to linear easing function.
function App() {
return <Animated.View entering={keyframe} />;
}
```
patrycjakalinska marked this conversation as resolved.
Show resolved Hide resolved

```js
import { Keyframe, Easing } from 'react-native-reanimated';
<details>
<summary>Type definitions</summary>

const keyframe = new Keyframe({
0: {
transform: [{ rotate: '0deg' }],
},
45: {
transform: [{ rotate: '100deg' }],
easing: Easing.exp,
},
100: {
transform: [{ rotate: '45deg' }],
},
});
```typescript
class Keyframe {
constructor(definitions: Record<string, KeyframeProps>);
duration(durationMs: number): Keyframe;
delay(delayMs: number): Keyframe;
reduceMotion(reduceMotionV: ReduceMotion): Keyframe;
withCallback(callback: (finished: boolean) => void): Keyframe;
}
```

## How to use keyframe animations?
</details>

Currently, you can define animations using keyframes only for entry and exit animations.
### Arguments

### 1. Choose Animated Component which entering or exiting you want to animate
#### `definitions`

```js
// AnimatedComponent - component created by createAnimatedComponent or imported from Reanimated
// keyframe - Keyframe object
<AnimatedComponent exiting={keyframe} />
```
An object, that contains definitions of your animation.
The object keys should be within range `0-100` and correspond to animation progress.
The object values should consist of style props and/or [easing function](/docs/animations/withTiming/#easing). If easing property is not provided, it defaults to `Easing.linear`.

### 2. Customize the animation
The keys take the following values:

```js
<AnimatedComponent exiting={keyframe.duration(3000).delay(200)} />
```
- `0` or `from`: Initial state of the object.
- Middle points (e.g., `45`): Intermediate states of the object.
- `100` or `to`: Final state of the object.
patrycjakalinska marked this conversation as resolved.
Show resolved Hide resolved

## Available modifiers
The key `0` (or `from`) should be assigned the style that you want for your object at the beginning of the animation. The key `100` (or `to`) should be assigned the style that you want for your object at the end of the animation.

The order of modifiers doesn't matter.
## Modifiers

### duration
```javascript
keyframe
.duration(1000)
.delay(500)
.reduceMotion(ReduceMotion.Never)
.withCallback((finished) => {
console.log(`finished without interruptions: ${finished}`);
});
```

default: 500
How long the animation should last.
- `.duration(durationMs: number)` is the length of the animation (in milliseconds). Defaults to `500`.
- `.delay(durationMs: number)` is the delay before the animation starts (in milliseconds). Defaults to `0`.
- `.reduceMotion(reduceMotion: ReduceMotion)` determines how the animation responds to the device's reduced motion accessibility setting.
- `.withCallback(callback: (finished: boolean) => void)` is the callback that will fire after the animation ends. Sets `finished` to `true` when animation ends without interruptions, and `false` otherwise.

### delay
## Remarks

default: 0
Allows to start with a specified delay.
- Providing keyframe `0` or `from` is required as it contains the initial state of the object you want to animate.
- Ensure you provide the initial value for all style properties you want to animate in other keyframes.
- Do not provide both `0` and `from`, or `100` and `to` keyframes, as it will result in a parsing conflict.
- If you want to animate transform style, make sure that all properties in the transformation array are in the same order in all keyframes.

### reduceMotion
## Example
patrycjakalinska marked this conversation as resolved.
Show resolved Hide resolved

default: ReduceMotion.System
Determines how the animation responds to the device's reduced motion accessibility setting.
<InteractiveExample src={KeyframeSrc} component={Keyframe} />

### withCallback
## Platform compatibility

Allows to execute code when keyframe animation ends.
<div className="platform-compatibility">

## Example

```js
export function KeyframeAnimation() {
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 && (
<Animated.View
entering={enteringAnimation}
exiting={exitingAnimation}
style={{
height: 100,
width: 200,
backgroundColor: 'blue',
alignItems: 'center',
justifyContent: 'center',
}}
/>
)}
</View>
</View>
);
}
```
| Android | iOS | Web |
| ------- | --- | --- |
| ✅ | ✅ | ✅ |

<video
src="https://user-images.githubusercontent.com/48885911/125463255-04502655-3147-4d15-ae5b-f327666eadff.mov"
controls="controls"
muted="muted"></video>
</div>
97 changes: 97 additions & 0 deletions packages/docs-reanimated/src/examples/KeyframeAnimation.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
import React, { useState } from 'react';
import { View, Button, StyleSheet, Pressable } from 'react-native';
import Animated, { Keyframe, Easing } from 'react-native-reanimated';

export default function KeyframeExample() {
const [visible, setVisible] = useState(true);

const enteringAnimation = new Keyframe({
0: {
opacity: 0,
transform: [
{ translateY: 50 },
{ rotate: '820deg' },
{ skewX: '0deg' },
{ scale: 0 },
],
},
50: {
opacity: 0.5,
transform: [
{ translateY: 25 },
{ rotate: '-180deg' },
{ skewX: '30deg' },
{ scale: 0.5 },
],
easing: Easing.out(Easing.quad),
},
100: {
opacity: 1,
transform: [
{ translateY: 0 },
{ rotate: '0deg' },
{ skewX: '0deg' },
{ scale: 1 },
],
},
}).duration(1000);

const exitingAnimation = new Keyframe({
0: {
opacity: 1,
transform: [{ translateY: 0 }, { rotateZ: '0deg' }],
},
10: {
opacity: 1,
transform: [{ translateY: 25 }, { rotateZ: '0deg' }],
easing: Easing.exp,
},
50: {
opacity: 0.5,
transform: [{ translateY: -100 }, { rotateZ: '60deg' }],
},
100: {
opacity: 0,
transform: [{ translateY: -300 }, { rotateZ: '120deg' }],
},
}).duration(1000);

return (
<View style={styles.container}>
{visible && (
<Animated.View
entering={enteringAnimation}
exiting={exitingAnimation}
style={styles.box}>
<Pressable
onPress={() => setVisible(!visible)}
style={styles.button}
/>
</Animated.View>
)}
<Button title="Animate box" onPress={() => setVisible(!visible)} />
</View>
);
}

const styles = StyleSheet.create({
container: {
height: 250,
flex: 1,
justifyContent: 'flex-end',
alignItems: 'center',
},
button: {
height: '100%',
width: '100%',
},
box: {
width: 100,
height: 100,
backgroundColor: '#b58df1',
borderRadius: 24,
justifyContent: 'center',
alignItems: 'center',
marginBottom: 48,
},
});