Skip to content

Commit

Permalink
feat(✨): New Transitions functions
Browse files Browse the repository at this point in the history
BREAKING CHANGE: This version includes support for Reanimated 1.4.0 and drops support for earlier versions. It also remove legacy functions to deal with transitions: toggle, useToggle, useTransition
  • Loading branch information
wcandillon authored Dec 12, 2019
1 parent 06025f4 commit 983a3ca
Show file tree
Hide file tree
Showing 13 changed files with 3,894 additions and 2,007 deletions.
2 changes: 1 addition & 1 deletion .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ jobs:
build:
docker:
# specify the version you desire here
- image: circleci/node:8.10.0
- image: circleci/node:10.17.0

# Specify service dependencies here if necessary
# CircleCI maintains a library of pre-built images
Expand Down
15 changes: 10 additions & 5 deletions .eslintrc
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
{
"extends": "react-native-wcandillon",
"rules": {
"react-native/no-color-literals": 0,
"import/prefer-default-export": 0,
"import/no-unresolved": 0,
"import/export": 0
"import/extensions": [
2,
{
"extensions": [
".ts",
".tsx"
]
}
]
}
}
}
61 changes: 22 additions & 39 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,28 @@ Returns 1 if the node's value is contained in the array of nodes, 0 otherwise.
contains(values: Node[], value: Node) => Node
```

## Transitions

Transitions are essential to the user experience.
Redash offers four utility functions for transitions which are broke down in the table below.
If you want to build a transition based on a React state change use `useTimingTransition()` or ``.
Are you transitioning a React state change or an animation value change?
And would you like to transition to use a timing a or a spring function?

| | State (JS Thread) | Value (UI Thread) |
| ------ |:----------------------:| ------------------------:|
| Timing | useTimingTransition() | withTimingTransition() |
| Spring | useSpringTransition() | withSpringTransition() |

### Example

```tsx
const Toggle = () => {
const [open, setOpen] = useState(false);
const transition = useTimingTransition(open, { duration: 400 });
}
```

## Animations

### `useValues(...Default Values[], deps)`
Expand All @@ -260,16 +282,6 @@ is a shortcut for
const [toggle state] = useMemoOne(() => [new Value(0), new Value(State.UNDETERMINED)], []);
```


### `useTransition(state: any, source: Node, destination: Node, duration: number, easing: EasingFunction): Node`

Returns an animation value that follows a component state.
The value equals `source` at the beginning of the transition and `destination` at the end of the transition.

### `useToggle(toggle: boolean, duration: number, easing: EasingFunction): Node`

Returns an animation value that follows a component boolean state.

### `timing({ clock?: Clock, from?: Node, to?: Node, duration?: Node, easing?: EasingFunction }): Node`

Convenience function to run a timing animation.
Expand Down Expand Up @@ -322,35 +334,6 @@ Convenience function to run a spring animation.
spring({ clock?: Clock, from?: Node, velocity?: number, config?: SpringConfig, to?: Node }): Node
```
### `toggle({ toggleState: Value, clock?: Clock, from?: Node, to?: Node, duration?: Node, easing?: EasingFunction }): Node`
Convenience function to imperatively toggle animation.
Example usage:
```tsx
const toggleState = new Value(State.END);

const handleToggleButtonPress = (shouldOpen: boolean) => {
shouldOpen ? toggleState.setValue(1) : toggleState.setValue(0);
};

useCode(
block([
toggle({
clock: Clock,
value: Node,
toggleState: Value(State.END),
easing: EasingFunction,
duration: number,
from: Node,
to: Node
})
]),
[]
);
```
### `bInterpolate(node: Node, from: Node, to: Node): Node`
Interpolate the node from 0 to 1 without clamping.
Expand Down
14 changes: 7 additions & 7 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,14 +28,14 @@
},
"devDependencies": {
"@react-native-community/bob": "^0.4.1",
"@types/react": "^16.8.15",
"@types/react-native": "^0.57.51",
"eslint": "^5.16.0",
"eslint-config-react-native-wcandillon": "2.0.4",
"@types/react": "*",
"@types/react-native": "^0.60.25",
"eslint": "^6.7.2",
"eslint-config-react-native-wcandillon": "2.3.0",
"react": "^16.8.6",
"react-native": "^0.59.5",
"react-native-gesture-handler": "^1.2.1",
"react-native-reanimated": "^1.2.0",
"react-native": "^0.61.0",
"react-native-gesture-handler": "~1.5.0",
"react-native-reanimated": "^1.4.0",
"semantic-release": "^15.13.3",
"semantic-release-cli": "^4.1.2",
"typescript": "^3.6.2"
Expand Down
54 changes: 1 addition & 53 deletions src/AnimationRunners.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import Animated, { Easing } from "react-native-reanimated";
import { SpringConfig } from "./Animations";

const {
Clock,
Value,
block,
cond,
eq,
stopClock,
set,
startClock,
Expand Down Expand Up @@ -150,8 +150,6 @@ export const decay = (params: DecayParams) => {
]);
};

export type SpringConfig = Partial<Omit<Animated.SpringConfig, "toValue">>;

export interface SpringParams {
clock?: Animated.Clock;
from?: Animated.Adaptable<number>;
Expand Down Expand Up @@ -249,53 +247,3 @@ export const loop = (loopConfig: LoopProps) => {
state.position
]);
};

export const toggle = (params: {
clock?: Animated.Clock;
toggleState: Animated.Value<number>;
duration?: number;
from: Animated.Adaptable<number>;
to: Animated.Adaptable<number>;
easing?: Animated.EasingFunction;
value: Animated.Value<number>;
}) => {
const { toggleState, easing, duration, value, to, from, clock } = {
clock: new Clock(),
duration: 250,
easing: Easing.linear,
from: new Value(0),
toggleState: new Value(-1),
to: new Value(1),
value: new Value(0),
...params
};

return [
cond(eq(toggleState, 1), [
set(
value,
timing({
clock,
duration,
easing,
from: value,
to
})
),
cond(not(clockRunning(clock)), [set(toggleState, -1)])
]),
cond(eq(toggleState, 0), [
set(
value,
timing({
clock,
duration,
easing,
from: value,
to: from
})
),
cond(not(clockRunning(clock)), [set(toggleState, -1)])
])
];
};
84 changes: 5 additions & 79 deletions src/Animations.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
import * as React from "react";
import Animated, { Easing } from "react-native-reanimated";
import Animated from "react-native-reanimated";
import { useMemoOne } from "use-memo-one";

import { TransformsStyle } from "react-native";
import { min } from "./Math";
import { timing } from "./AnimationRunners";

const {
Value,
Expand All @@ -16,26 +13,19 @@ const {
abs,
sub,
interpolate,
divide,
useCode,
not,
defined,
neq,
diff,
lessThan,
greaterThan
} = Animated;

type AnimatedTransform = {
[P in keyof TransformsStyle["transform"]]: Animated.Adaptable<
TransformsStyle["transform"][P]
>
};
export type SpringConfig = Partial<Omit<Animated.SpringConfig, "toValue">>;
export type TimingConfig = Partial<Omit<Animated.TimingConfig, "toValue">>;

export const moving = (
position: Animated.Node<number>,
minPositionDelta: number = 1e-3,
emptyFrameThreshold: number = 5
minPositionDelta = 1e-3,
emptyFrameThreshold = 5
) => {
const delta = diff(position);
const noMovementFrames = new Value(0);
Expand Down Expand Up @@ -74,70 +64,6 @@ export const bInterpolate = (
outputRange: [origin, destination]
});

export const translateZ = (
perspective: Animated.Adaptable<number>,
z: Animated.Adaptable<number>
) => ({ scale: divide(perspective, sub(perspective, z)) });

export const transformOrigin = (
x: Animated.Adaptable<number> = 0,
y: Animated.Adaptable<number> = 0,
...transformations: AnimatedTransform[]
): AnimatedTransform[] => [
{ translateX: x },
{ translateY: y },
...transformations,
{ translateX: multiply(x, -1) },
{ translateY: multiply(y, -1) }
];

export const useTransition = <T extends unknown>(
state: T,
src: Animated.Adaptable<number>,
dest: Animated.Adaptable<number>,
duration: number = 400,
easing: Animated.EasingFunction = Easing.linear
) => {
if (!React.useMemo) {
throw new Error(
"useTransition() is only available in React Native 0.59 or higher."
);
}
const { transitionVal } = useMemoOne(
() => ({
transitionVal: new Value() as Animated.Value<number>
}),
[]
);
useCode(
cond(
not(defined(transitionVal)),
set(transitionVal, src),
cond(
neq(transitionVal, src),
set(
transitionVal,
timing({
from: dest,
to: src,
duration,
easing
})
)
)
),
[state]
);
return transitionVal;
};

export const useToggle = (
toggle: boolean,
duration: number = 400,
easing: Animated.EasingFunction = Easing.linear
) =>
useTransition(toggle, toggle ? 1 : 0, not(toggle ? 1 : 0), duration, easing);

type Dependencies = readonly unknown[];
type Atomic = string | number | boolean;

Expand Down
3 changes: 2 additions & 1 deletion src/Gesture.ts
Original file line number Diff line number Diff line change
Expand Up @@ -213,7 +213,8 @@ type NativeEvent = GestureHandlerStateChangeNativeEvent &
| RotationGestureHandlerEventExtra
| FlingGestureHandlerEventExtra
| PinchGestureHandlerEventExtra
| ForceTouchGestureHandlerEventExtra);
| ForceTouchGestureHandlerEventExtra
);

type Adaptable<T> = { [P in keyof T]: Animated.Adaptable<T[P]> };

Expand Down
2 changes: 1 addition & 1 deletion src/Math.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ export const between = (
value: Animated.Node<number>,
lowerBound: Animated.Adaptable<number>,
upperBound: Animated.Adaptable<number>,
inclusive: boolean = true
inclusive = true
) => {
if (inclusive) {
return and(greaterOrEq(value, lowerBound), lessOrEq(value, upperBound));
Expand Down
27 changes: 27 additions & 0 deletions src/Transformations.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import Animated from "react-native-reanimated";
import { TransformsStyle } from "react-native";

const { divide, sub, multiply } = Animated;

type AnimatedTransform = {
[P in keyof TransformsStyle["transform"]]: Animated.Adaptable<
TransformsStyle["transform"][P]
>;
};

export const translateZ = (
perspective: Animated.Adaptable<number>,
z: Animated.Adaptable<number>
) => ({ scale: divide(perspective, sub(perspective, z)) });

export const transformOrigin = (
x: Animated.Adaptable<number> = 0,
y: Animated.Adaptable<number> = 0,
...transformations: AnimatedTransform[]
): AnimatedTransform[] => [
{ translateX: x },
{ translateY: y },
...transformations,
{ translateX: multiply(x, -1) },
{ translateY: multiply(y, -1) }
];
Loading

0 comments on commit 983a3ca

Please sign in to comment.