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 Custom Animations page #6346

Merged
Merged
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
297 changes: 127 additions & 170 deletions packages/docs-reanimated/docs/layout-animations/custom-animations.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,15 @@ sidebar_position: 4

# Custom animations

import DocsCompatibilityInfo from '../_shared/_docs_compatibility_info.mdx';
import EnteringExitingAnimationSrc from '!!raw-loader!@site/src/examples/CustomEnteringExiting';
import LayoutAnimationSrc from '!!raw-loader!@site/src/examples/CustomLayoutAnimation';

<DocsCompatibilityInfo />
Custom animations give you a full control over the Entering/Exiting animations and Layout transitions. However, they tend to be hard to understand and maintain. We recommend starting with predefined [Entering/Exiting](docs/layout-animations/entering-exiting-animations), [Keyframes](/docs/layout-animations/keyframe-animations) and [Layout](/docs/layout-animations/layout-transitions) presets first before using custom animations.

If our set of predefined animations is not enough for you then this tab is what you are looking for.

## Custom Exiting Animation

What our exiting animation builders do under the hood is generating a worklet function that returns essential data for starting particular animation.
The high level template looks like this:
## Reference

```js
function CustomExitingAnimation(values) {
function CustomAnimation(values) {
'worklet';
const animations = {
// your animations
Expand All @@ -35,221 +31,182 @@ function CustomExitingAnimation(values) {
}
```

## Custom Exiting Animation

<details>
<summary>Type definitions</summary>

```typescript
function CustomExitTransition (values: ExitAnimationsValues) => LayoutAnimation

type LayoutAnimation = {
initialValues: StyleProps;
animations: StyleProps;
callback?: (finished: boolean) => void;
};

type ExitAnimationsValues = CurrentLayoutAnimationsValues &
WindowDimensions;

type CurrentLayoutAnimationsValues = {
['currentOriginX', 'currentOriginY', 'currentWidth', 'currentHeight', 'currentBorderRadius', 'currentGlobalOriginX','currentGlobalOriginY']: number;
};

interface WindowDimensions {
windowWidth: number;
windowHeight: number;
}
```

</details>

### Arguments

- `values` - contains information about where view was displayed and what were its dimensions
- `values.currentOriginX` - X coordinate of top left corner in parent's coordinate system
- `values.currentOriginY` - Y coordinate of top left corner in parent's coordinate system
- `values.currentWidth` - view's width
- `values.currentHeight` - view's height
- `values.currentBorderRadius` - view's border radius
- `values.currentGlobalOriginX` - X coordinate of top left corner in global coordinate system
- `values.currentGlobalOriginY` - Y coordinate of top left corner in global coordinate system

### Example

```js
function CardView() {
const exiting = (values) => {
'worklet';
const animations = {
originX: withTiming(width, { duration: 3000 }),
opacity: withTiming(0.5, { duration: 2000 }),
};
const initialValues = {
originX: values.currentOriginX,
opacity: 1,
};
return {
initialValues,
animations,
};
};
<ExampleVideo
sources={{
android:
'/react-native-reanimated/recordings/custom_animations/android_exiting_custom.mov',
ios: '/react-native-reanimated/recordings/custom_animations/ios_exiting_custom.mov',
}}
/>

return (
<Animated.View style={[styles.animatedView]} exiting={exiting}>
<Text> Card Example </Text>
</Animated.View>
);
}
```
<CollapsibleCode src={EnteringExitingAnimationSrc} showLines={[29, 45]} />

## Custom Entering Animation
patrycjakalinska marked this conversation as resolved.
Show resolved Hide resolved

What our entering animation builders do under the hood is generating a worklet function that returns essential data for starting particular animation.
The high level template looks like this:
<details>
<summary>Type definitions</summary>

```js
function CustomEnteringAnimation(values) {
'worklet';
const animations = {
// your animations
};
const initialValues = {
// initial values for animations
};
const callback = (finished: boolean) => {
// optional callback that will fire when layout animation ends
};
return {
initialValues,
animations,
callback,
};
```typescript
function CustomEntryTransition (values: EntryAnimationsValues) => LayoutAnimation

type LayoutAnimation = {
initialValues: StyleProps;
animations: StyleProps;
callback?: (finished: boolean) => void;
};

type EntryAnimationsValues = TargetLayoutAnimationsValues &
WindowDimensions;

type TargetLayoutAnimationsValues = {
['targetOriginX', 'targetOriginY', 'targetWidth', 'targetHeight', 'targetBorderRadius', 'targetGlobalOriginX','targetGlobalOriginY']: number;
};

interface WindowDimensions {
windowWidth: number;
windowHeight: number;
}
```

</details>

### Arguments

- `values` - contains information about where view wants to be displayed and what are its dimensions
- `values.targetOriginX` - X coordinate of top left corner in parent's coordinate system
- `values.targetOriginY` - Y coordinate of top left corner in parent's coordinate system
- `values.targetWidth` - view's width
- `values.targetHeight` - view's height
- `values.targetBorderRadius` - view's border radius
- `values.targetGlobalOriginX` - X coordinate of top left corder in global coordinate system
- `values.targetGlobalOriginY` - Y coordinate of top left corder in global coordinate system

### Example

```js
function CardView() {
const entering = (targetValues) => {
'worklet';
const animations = {
originX: withTiming(targetValues.targetOriginX, { duration: 3000 }),
opacity: withTiming(1, { duration: 2000 }),
borderRadius: withDelay(4000, withTiming(30, { duration: 3000 })),
transform: [
{ rotate: withTiming('0deg', { duration: 4000 }) },
{ scale: withTiming(1, { duration: 3500 }) },
],
};
const initialValues = {
originX: -width,
opacity: 0,
borderRadius: 10,
transform: [{ rotate: '90deg' }, { scale: 0.5 }],
};
return {
initialValues,
animations,
};
};
<ExampleVideo
sources={{
android:
'/react-native-reanimated/recordings/custom_animations/android_entering_custom.mov',
ios: '/react-native-reanimated/recordings/custom_animations/ios_entering_custom.mov',
}}
/>

return (
<Animated.View style={[styles.animatedView]} entering={entering}>
<Text> Card Example </Text>
</Animated.View>
);
}
```
<CollapsibleCode src={EnteringExitingAnimationSrc} showLines={[6, 28]} />

## Custom Layout Transition

What our layout transition builders do under the hood is generating a worklet function that returns essential data for starting particular transition.
The high level template looks like this:
<details>
<summary>Type definitions</summary>

```js
function CustomLayoutTransition(values) {
'worklet';
const animations = {
// your animations
};
const initialValues = {
// initial values for animations
};
const callback = (finished: boolean) => {
// optional callback that will fire when layout animation ends
};
return {
initialValues,
animations,
callback,
};
```typescript
function CustomLayoutTransition (values: LayoutAnimationValues) => LayoutAnimation

type LayoutAnimation = {
initialValues: StyleProps;
animations: StyleProps;
callback?: (finished: boolean) => void;
};

type LayoutAnimationsValues = CurrentLayoutAnimationsValues & TargetLayoutAnimationsValues & WindowDimensions;

type CurrentLayoutAnimationsValues = {
['currentOriginX', 'currentOriginY', 'currentWidth', 'currentHeight', 'currentBorderRadius', 'currentGlobalOriginX','currentGlobalOriginY']: number;
};

type TargetLayoutAnimationsValues = {
['targetOriginX', 'targetOriginY', 'targetWidth', 'targetHeight', 'targetBorderRadius', 'targetGlobalOriginX','targetGlobalOriginY']: number;
};

interface WindowDimensions {
windowWidth: number;
windowHeight: number;
}
```

</details>

### Arguments

- `values` - contains before and after information about the view's origin and dimensions
- `values.targetOriginX` - X coordinate of top left corner in parent's coordinate system
- `values.targetOriginY` - Y coordinate of top left corner in parent's coordinate system
- `values.targetWidth` - view's width
- `values.targetHeight` - view's height
- `values.targetBorderRadius` - view's border radius
- `values.targetGlobalOriginX` - X coordinate of top left corder in global coordinate system
- `values.targetGlobalOriginY` - Y coordinate of top left corder in global coordinate system
- `values.currentOriginX` - X coordinate of top left corner in parent's coordinate system (before)
- `values.currentOriginY` - Y coordinate of top left corner in parent's coordinate system (before)
- `values.currentWidth` - view's width (before)
- `values.currentHeight` - view's height (before)
- `values.currentBorderRadius` - view's border radius (before)
- `values.currentGlobalOriginX` - X coordinate of top left corner in global coordinate system (before)
- `values.currentGlobalOriginY` - Y coordinate of top left corner in global coordinate system (before)

### Example

<video
src="https://user-images.githubusercontent.com/12784455/120450759-09fa3980-c391-11eb-9b64-65ec8e6c2509.mp4"
controls="controls"
muted="muted"
width="45%"></video>
<ExampleVideo
sources={{
android:
'/react-native-reanimated/recordings/custom_animations/android_layout_custom.mov',
ios: '/react-native-reanimated/recordings/custom_animations/ios_layout_custom.mov',
}}
/>

```js
function CustomLayoutTransition(values) {
'worklet';
return {
animations: {
originX: withTiming(values.targetOriginX, { duration: 1000 }),
originY: withDelay(
1000,
withTiming(values.targetOriginY, { duration: 1000 })
),
width: withSpring(values.targetWidth),
height: withSpring(values.targetHeight),
},
initialValues: {
originX: values.currentOriginX,
originY: values.currentOriginY,
width: values.currentWidth,
height: values.currentHeight,
},
};
}
<CollapsibleCode src={LayoutAnimationSrc} showLines={[8, 28]} />

function Box({ label, state }: { label: string, state: boolean }) {
const ind = label.charCodeAt(0) - 'A'.charCodeAt(0);
const delay = 300 * ind;
return (
<Animated.View
layout={CustomLayoutTransition}
style={[
styles.box,
{
flexDirection: state ? 'row' : 'row-reverse',
height: state ? 30 : 60,
},
]}>
<Text> {label} </Text>
</Animated.View>
);
}
## Remarks

export function CustomLayoutTransitionExample() {
const [state, setState] = useState(true);
return (
<View style={{ marginTop: 30 }}>
<View style={{ height: 300 }}>
<View style={{ flexDirection: state ? 'row' : 'column' }}>
{state && <Box key="a" label="A" state={state} />}
<Box key="b" label="B" state={state} />
{!state && <Box key="a" label="A" state={state} />}
<Box key="c" label="C" state={state} />
</View>
</View>

<Button
onPress={() => {
setState(!state);
}}
title="toggle"
/>
</View>
);
}
```
- Each Reanimated component has a shared value that keeps the current animations assigned to that particular component. If you start a new animation for a specific property without providing an initial value for that property, the initial value will be taken from the last animation assigned to the component. The only exception is the `Entering` animation, as we have no way to get the previous animation values.

## Platform compatibility

<div className="platform-compatibility">

## Other Facts
| Android | iOS | Web |
| ------- | --- | --- |
| ✅ | ✅ | ❌ |

Each Reanimated component has its shared value that keeps current animations assigned to that particular component. If you want to start a new animation for a specific prop and you don't provide an initial value for the prop then the initial value will be taken from the last animation that has been assigned to the component. The only exception is Entering animation because we have no way to get the previous animation values.
</div>
Loading