Skip to content

Commit

Permalink
feat(📉): cubicBezier, get​Point​AtLength, and interpolatePath (#43)
Browse files Browse the repository at this point in the history
Breaking change:
* SVG function API has changed to be symmetric with [getTotalLength](https://developer.mozilla.org/en-US/docs/Web/API/SVGPathElement/getTotalLength) and [getPointAtLength](https://developer.mozilla.org/en-US/docs/Web/API/SVGPathElement/getPointAtLength)
* binaryInterpolation has been renamed to bInterpolate
  • Loading branch information
wcandillon authored Jun 3, 2019
1 parent 947e13c commit a2c66f0
Show file tree
Hide file tree
Showing 14 changed files with 1,432 additions and 337 deletions.
76 changes: 65 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,63 @@ Example usage:
<ReText text={new Value("hello world!")} style={{ color: "blue" }} />
```

## SVG

### `parsePath(SVGPath: String): ReanimatedPath`

Given an SVG Path, returns an denormalized object of values that can be used for animations on that path.
From the perspective of the user, the returned value should be considered a black box.
Here is an example below:

```ts
// We get the data from the SVG Path denormalized a way that can be handled with Reanimated
const path = parsePath(d);
const { y, x } = getPointAtLength(path, length);
```

### `getPointAtLength(path): { x: Node, y: Node }`

Implementation of (getPointAtLength)[https://developer.mozilla.org/en-US/docs/Web/API/SVGPathElement/getPointAtLength] for Reanimated.

```ts
// We get the data from the SVG Path denormalized a way that can be handled with Reanimated
const path = parsePath(d);
const { y, x } = getPointAtLength(path, length);
```

### `interpolatePath(path1, path2, progress): path`

Interpolate from one SVG point to the other, this function assumes that each path has the same number of points.

```tsx
const rhino = "M 217.765 29.683 C 225.855 29.683 ";
const rhinoPath = parsePath(rhino);
const elephant = "M 223.174 43.413 ...";
const elephantPath = parsePath(elephant);
return (
<>
<Animated.Code>
{() =>
set(
progress,
runTiming(clock, progress, {
toValue: 1,
duration: 2000,
easing: Easing.linear
})
)
}
</Animated.Code>
<Svg style={styles.container} viewBox="0 0 409 280">
<AnimatedPath
d={interpolatePath(rhinoPath, elephantPath, progress)}
fill="#7d8f9b"
/>
</Svg>
</>
);
```

## Math

### `toRad(node)`
Expand Down Expand Up @@ -93,6 +150,13 @@ Returns the angle in the plane (in radians) between the positive x-axis and the
atan2(y: Node, x Node) => Node
```

### `cubicBezier(t, p0, p1, p2, p3)`

Returns the coordinate of a cubic bezier curve.
`t` is the length of the curve from 0 to 1. `cubicBezier(0, p0, p1, p2, p3) => p0` and `cubicBezier(1, p0, p1, p2, p3) => p3`.
`p0` and `p3` are respectively the starting and ending point of the curve.
`p1` and `p2` are the control points.

## Arrays

### `find(nodes, index, notFound)`
Expand All @@ -111,16 +175,6 @@ Returns 1 if the node value is contained in the array of nodes, 0 otherwise.
contains(values: Node[], value: Node) => Node
```

## SVG

### `getX(SVGPath: String, y: Node): Node`

Given an SVG Path, returns the `x` coordinate of the path given an `y` coordinate.

### `getY(SVGPath: String, x: Node): Node`

Given an SVG Path, returns the `y` coordinate of the path given an `x` coordinate.

## Animations

### `runTiming(clock, value, config)`
Expand Down Expand Up @@ -150,7 +204,7 @@ Convenience function to run a decay animation.
runDecay(clock: Clock, value: Node, velocity: Node, rerunDecaying: Node): Node
```

### `binaryInterpolation(node, from, to)`
### `bInterpolate(node, from, to)`

Interpolate the node from 0 to 1 without clamping.

Expand Down
6 changes: 4 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
"@types/react": "^16.8.15",
"@types/react-native": "^0.57.51",
"eslint": "^5.16.0",
"eslint-config-react-native-wcandillon": "^1.0.6",
"eslint-config-react-native-wcandillon": "1.1.3",
"react": "^16.8.6",
"react-native": "^0.59.5",
"react-native-gesture-handler": "^1.2.1",
Expand All @@ -55,6 +55,8 @@
]
},
"dependencies": {
"svg-path-properties": "0.4.8"
"abs-svg-path": "^0.1.1",
"normalize-svg-path": "^1.0.1",
"parse-svg-path": "^0.1.2"
}
}
32 changes: 16 additions & 16 deletions src/AnimationRunners.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,24 +11,24 @@ const {
set,
startClock,
clockRunning,
onChange,
onChange
} = Animated;

export function runDecay(
clock: Animated.Clock,
value: Animated.Adaptable<number>,
velocity: Animated.Adaptable<number>,
rerunDecaying: Animated.Value<number>,
rerunDecaying: Animated.Value<number>
) {
const state = {
finished: new Value(0),
velocity: new Value(0),
position: new Value(0),
time: new Value(0),
time: new Value(0)
};

const config = {
deceleration: 0.99,
deceleration: 0.99
};

return [
Expand All @@ -39,12 +39,12 @@ export function runDecay(
set(state.velocity, velocity),
set(state.position, value),
set(state.time, 0),
startClock(clock),
]),
startClock(clock)
])
]),
decay(clock, state, config),
cond(state.finished, stopClock(clock)),
state.position,
state.position
];
}

Expand All @@ -59,14 +59,14 @@ export function runSpring(
stiffness: 121.6,
overshootClamping: false,
restSpeedThreshold: 0.001,
restDisplacementThreshold: 0.001,
},
restDisplacementThreshold: 0.001
}
) {
const state = {
finished: new Value(0),
velocity: new Value(0),
position: new Value(0),
time: new Value(0),
time: new Value(0)
};

return block([
Expand All @@ -76,24 +76,24 @@ export function runSpring(
set(state.position, value),
set(state.velocity, 0),
set(config.toValue as any, dest),
startClock(clock),
startClock(clock)
]),
spring(clock, state, config),
cond(state.finished, stopClock(clock)),
state.position,
state.position
]);
}

export function runTiming(
clock: Animated.Clock,
value: Animated.Adaptable<any>,
config: Animated.TimingConfig,
config: Animated.TimingConfig
) {
const state = {
finished: new Value(0),
position: new Value(0),
time: new Value(0),
frameTime: new Value(0),
frameTime: new Value(0)
};

return block([
Expand All @@ -103,10 +103,10 @@ export function runTiming(
set(state.time, 0),
set(state.position, value),
set(state.frameTime, 0),
startClock(clock),
startClock(clock)
]),
timing(clock, state, config),
cond(state.finished, stopClock(clock)),
state.position,
state.position
]);
}
46 changes: 46 additions & 0 deletions src/Animations.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import Animated from "react-native-reanimated";

import { min } from "./Math";

const {
Value,
add,
multiply,
cond,
eq,
abs,
sub,
interpolate,
divide
} = Animated;

export const snapPoint = (
value: Animated.Adaptable<number>,
velocity: Animated.Adaptable<number>,
points: number[]
) => {
const point = add(value, multiply(0.2, velocity));
const diffPoint = (p: Animated.Adaptable<number>) => abs(sub(point, p));
const deltas = points.map(p => diffPoint(p));
const minDelta = min(...deltas);
return points.reduce(
(acc: Animated.Node<any>, p: number) =>
cond(eq(diffPoint(p), minDelta), p, acc),
new Value()
);
};

export const bInterpolate = (
value: Animated.Adaptable<number>,
origin: Animated.Adaptable<number>,
destination: Animated.Adaptable<number>
) =>
interpolate(value, {
inputRange: [0, 1],
outputRange: [origin, destination]
});

export const translateZ = (
perspective: Animated.Adaptable<number>,
z: Animated.Adaptable<number>
) => ({ scale: divide(perspective, sub(perspective, z)) });
11 changes: 5 additions & 6 deletions src/Arrays.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,15 @@
import Animated from "react-native-reanimated";

const {
Value, cond, eq, or,
} = Animated;
const { Value, cond, eq, or } = Animated;

export const find = (
array: Animated.Adaptable<number>[],
index: Animated.Adaptable<number>,
notFound: Animated.Node<any> = new Value(),
notFound: Animated.Node<any> = new Value()
) => array.reduce((acc, v, i) => cond(eq(i, index), v, acc), notFound);

export const contains = (
values: Animated.Node<number>[],
value: Animated.Node<number>,
): Animated.Node<number> => values.reduce((acc, v) => or(acc, eq(value, v)), new Value(0));
value: Animated.Node<number>
): Animated.Node<number> =>
values.reduce((acc, v) => or(acc, eq(value, v)), new Value(0));
Loading

0 comments on commit a2c66f0

Please sign in to comment.