mirror of
https://github.com/zhigang1992/react-native-reanimated.git
synced 2026-01-12 09:24:04 +08:00
Add SpringUtils.js (#40)
Motivation API known form RN core is much more flexible when it comes to being flexible. We wanted to make using spring animation easier. Changes We didn't want to modify existing spring object so I have added SpringUtils object which is a set of methods for easier spring's config manipulations.
This commit is contained in:
@@ -16,6 +16,7 @@ import Rotations from './rotations';
|
||||
import Imperative from './imperative';
|
||||
import PanRotateAndZoom from './PanRotateAndZoom';
|
||||
import ProgressBar from './progressBar';
|
||||
import DifferentSpringConfigs from './differentSpringConfigs';
|
||||
import TransitionsSequence from './transitions/sequence';
|
||||
import TransitionsShuffle from './transitions/shuffle';
|
||||
import TransitionsProgress from './transitions/progress';
|
||||
@@ -56,6 +57,10 @@ const SCREENS = {
|
||||
screen: ProgressBar,
|
||||
title: 'Progress bar',
|
||||
},
|
||||
differentSpringConfigs: {
|
||||
screen: DifferentSpringConfigs,
|
||||
title: 'Different Spring Configs',
|
||||
},
|
||||
transitionsSequence: {
|
||||
screen: TransitionsSequence,
|
||||
title: 'Transitions sequence',
|
||||
|
||||
102
Example/differentSpringConfigs/index.js
Normal file
102
Example/differentSpringConfigs/index.js
Normal file
@@ -0,0 +1,102 @@
|
||||
import React, { Component } from 'react';
|
||||
import { StyleSheet, View } from 'react-native';
|
||||
import Animated from 'react-native-reanimated';
|
||||
|
||||
const {
|
||||
block,
|
||||
set,
|
||||
cond,
|
||||
eq,
|
||||
spring,
|
||||
startClock,
|
||||
Value,
|
||||
Clock,
|
||||
SpringUtils,
|
||||
} = Animated;
|
||||
|
||||
function runSpring(clock, value, config) {
|
||||
const state = {
|
||||
finished: new Value(1),
|
||||
velocity: new Value(0),
|
||||
position: new Value(0),
|
||||
time: new Value(0),
|
||||
};
|
||||
|
||||
return block([
|
||||
cond(state.finished, [
|
||||
set(state.finished, 0),
|
||||
set(state.position, value),
|
||||
set(config.toValue, cond(eq(config.toValue, 100), -100, 100)),
|
||||
startClock(clock),
|
||||
]),
|
||||
spring(clock, state, config),
|
||||
state.position,
|
||||
]);
|
||||
}
|
||||
|
||||
class Snappable extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
const transX = new Value();
|
||||
const clock = new Clock();
|
||||
this._transX = runSpring(clock, transX, props.config);
|
||||
}
|
||||
render() {
|
||||
const { children } = this.props;
|
||||
return (
|
||||
<Animated.View style={{ transform: [{ translateX: this._transX }] }}>
|
||||
{children}
|
||||
</Animated.View>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const configA = SpringUtils.makeDefaultConfig();
|
||||
const configB = SpringUtils.makeConfigFromBouncinessAndSpeed({
|
||||
...SpringUtils.makeDefaultConfig(),
|
||||
bounciness: 10,
|
||||
speed: 8,
|
||||
});
|
||||
const configC = SpringUtils.makeConfigFromOrigamiTensionAndFriction({
|
||||
...SpringUtils.makeDefaultConfig(),
|
||||
tension: 10,
|
||||
friction: new Value(4),
|
||||
});
|
||||
|
||||
export default class Example extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
<Snappable config={configA}>
|
||||
<View style={styles.box} />
|
||||
</Snappable>
|
||||
<Snappable config={configB}>
|
||||
<View style={styles.box} />
|
||||
</Snappable>
|
||||
<Snappable config={configC}>
|
||||
<View style={styles.box} />
|
||||
</Snappable>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const CIRCLE_SIZE = 70;
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
},
|
||||
box: {
|
||||
backgroundColor: 'tomato',
|
||||
marginLeft: -(CIRCLE_SIZE / 2),
|
||||
marginTop: -(CIRCLE_SIZE / 2),
|
||||
width: CIRCLE_SIZE,
|
||||
height: CIRCLE_SIZE,
|
||||
margin: CIRCLE_SIZE,
|
||||
borderRadius: CIRCLE_SIZE / 2,
|
||||
borderColor: '#000',
|
||||
},
|
||||
});
|
||||
25
README.md
25
README.md
@@ -729,6 +729,31 @@ spring(clock, { finished, position, velocity, time }, { damping, mass, stiffness
|
||||
|
||||
When evaluated, updates `position` and `velocity` nodes by running a single step of spring based animation. Check the original `Animated` API docs to learn about the config parameters like `damping`, `mass`, `stiffness`, `overshootClamping`, `restSpeedThreshold` and `restDisplacementThreshold`. The `finished` state updates to `1` when the `position` reaches the destination set by `toValue`. The `time` state variable also updates when the node evaluates and it represents the clock value at the time when the node got evaluated for the last time. It is expected that `time` variable is reset before spring animation can be restarted.
|
||||
|
||||
### `SpringUtils`
|
||||
For developers' convenience, it's possible to use a different way of configuring `spring` animation which follows behavior known from React Native core.
|
||||
|
||||
#### `SpringUtils.makeDefaultConfig()`
|
||||
Returns an object filled with default config of animation:
|
||||
```js
|
||||
{
|
||||
stiffness: new Value(100),
|
||||
mass: new Value(1),
|
||||
damping: new Value(10),
|
||||
overshootClamping: false,
|
||||
restSpeedThreshold: 0.001,
|
||||
restDisplacementThreshold: 0.001,
|
||||
toValue: new Value(0),
|
||||
}
|
||||
```
|
||||
|
||||
#### `SpringUtils.makeConfigFromBouncinessAndSpeed(prevConfig)`
|
||||
Transforms an object with `bounciness` and `speed` params into config expected by the `spring` node. `bounciness` and `speed` might be nodes or numbers.
|
||||
|
||||
#### `SpringUtils.makeConfigFromOrigamiTensionAndFriction(prevConfig)`
|
||||
Transforms an object with `tension` and `friction` params into config expected by the `spring` node. `tension` and `friction` might be nodes or numbers.
|
||||
|
||||
See an [Example of different configs](https://github.com/kmagiera/react-native-reanimated/blob/master/Example/colors/differentSpringConfigs.js).
|
||||
|
||||
|
||||
## Running animations
|
||||
### Declarative API
|
||||
|
||||
28
react-native-reanimated.d.ts
vendored
28
react-native-reanimated.d.ts
vendored
@@ -113,6 +113,34 @@ declare module 'react-native-reanimated' {
|
||||
toValue: Adaptable<number>;
|
||||
}
|
||||
|
||||
interface SpringConfigWithOrigamiTensionAndFriction {
|
||||
tension: Adaptable<number>;
|
||||
mass: Adaptable<number>;
|
||||
friction: Adaptable<number>;
|
||||
overshootClamping: Adaptable<number> | boolean;
|
||||
restSpeedThreshold: Adaptable<number>;
|
||||
restDisplacementThreshold: Adaptable<number>;
|
||||
toValue: Adaptable<number>;
|
||||
}
|
||||
|
||||
interface SpringConfigWithBouncinessAndSpeed {
|
||||
bounciness: Adaptable<number>;
|
||||
mass: Adaptable<number>;
|
||||
speed: Adaptable<number>;
|
||||
overshootClamping: Adaptable<number> | boolean;
|
||||
restSpeedThreshold: Adaptable<number>;
|
||||
restDisplacementThreshold: Adaptable<number>;
|
||||
toValue: Adaptable<number>;
|
||||
}
|
||||
|
||||
type SprintUtils = {
|
||||
makeDefaultConfig: () => SpringConfig;
|
||||
makeConfigFromBouncinessAndSpeed: (prevConfig: SpringConfigWithBouncinessAndSpeed) => SpringConfig;
|
||||
makeConfigFromOrigamiTensionAndFriction: (prevConfig: SpringConfigWithOrigamiTensionAndFriction) => SpringConfig
|
||||
}
|
||||
|
||||
export const SprintUtils: SprintUtils
|
||||
|
||||
type AnimateStyle<S extends object> = {
|
||||
[K in keyof S]: S[K] extends ReadonlyArray<any>
|
||||
? ReadonlyArray<AnimateStyle<S[K][0]>>
|
||||
|
||||
@@ -23,6 +23,7 @@ import {
|
||||
Transitioning,
|
||||
createTransitioningComponent,
|
||||
} from './Transitioning';
|
||||
import SpringUtils from './animations/SpringUtils';
|
||||
|
||||
const Animated = {
|
||||
// components
|
||||
@@ -46,6 +47,7 @@ const Animated = {
|
||||
decay: backwardCompatibleAnimWrapper(decay, DecayAnimation),
|
||||
timing: backwardCompatibleAnimWrapper(timing, TimingAnimation),
|
||||
spring: backwardCompatibleAnimWrapper(spring, SpringAnimation),
|
||||
SpringUtils,
|
||||
|
||||
// configuration
|
||||
addWhitelistedNativeProps,
|
||||
|
||||
199
src/animations/SpringUtils.js
Normal file
199
src/animations/SpringUtils.js
Normal file
@@ -0,0 +1,199 @@
|
||||
import {
|
||||
cond,
|
||||
sub,
|
||||
divide,
|
||||
multiply,
|
||||
add,
|
||||
pow,
|
||||
lessOrEq,
|
||||
and,
|
||||
greaterThan,
|
||||
} from './../base';
|
||||
import AnimatedValue from './../core/InternalAnimatedValue';
|
||||
|
||||
function stiffnessFromOrigamiValue(oValue) {
|
||||
return (oValue - 30) * 3.62 + 194;
|
||||
}
|
||||
|
||||
function dampingFromOrigamiValue(oValue) {
|
||||
return (oValue - 8) * 3 + 25;
|
||||
}
|
||||
|
||||
function stiffnessFromOrigamiNode(oValue) {
|
||||
return add(multiply(sub(oValue, 30), 3.62), 194);
|
||||
}
|
||||
|
||||
function dampingFromOrigamiNode(oValue) {
|
||||
return add(multiply(sub(oValue, 8), 3), 25);
|
||||
}
|
||||
|
||||
function makeConfigFromOrigamiTensionAndFriction(prevConfig) {
|
||||
const { tension, friction, ...rest } = prevConfig;
|
||||
return {
|
||||
...rest,
|
||||
stiffness:
|
||||
typeof tension === 'number'
|
||||
? stiffnessFromOrigamiValue(tension)
|
||||
: stiffnessFromOrigamiNode(tension),
|
||||
damping:
|
||||
typeof friction === 'number'
|
||||
? dampingFromOrigamiValue(friction)
|
||||
: dampingFromOrigamiNode(friction),
|
||||
};
|
||||
}
|
||||
|
||||
function makeConfigFromBouncinessAndSpeed(prevConfig) {
|
||||
const { bounciness, speed, ...rest } = prevConfig;
|
||||
if (typeof bounciness === 'number' && typeof speed === 'number') {
|
||||
return fromBouncinessAndSpeedNumbers(bounciness, speed, rest);
|
||||
}
|
||||
return fromBouncinessAndSpeedNodes(bounciness, speed, rest);
|
||||
}
|
||||
|
||||
function fromBouncinessAndSpeedNodes(bounciness, speed, rest) {
|
||||
function normalize(value, startValue, endValue) {
|
||||
return divide(sub(value, startValue), sub(endValue, startValue));
|
||||
}
|
||||
|
||||
function projectNormal(n, start, end) {
|
||||
return add(start, multiply(n, sub(end, start)));
|
||||
}
|
||||
|
||||
function linearInterpolation(t, start, end) {
|
||||
return add(multiply(t, end), multiply(sub(1, t), start));
|
||||
}
|
||||
|
||||
function quadraticOutInterpolation(t, start, end) {
|
||||
return linearInterpolation(sub(multiply(2, t), multiply(t, t)), start, end);
|
||||
}
|
||||
|
||||
function b3Friction1(x) {
|
||||
return add(
|
||||
sub(multiply(0.0007, pow(x, 3)), multiply(0.031, pow(x, 2))),
|
||||
multiply(0.64, x),
|
||||
1.28
|
||||
);
|
||||
}
|
||||
|
||||
function b3Friction2(x) {
|
||||
return add(
|
||||
sub(multiply(0.000044, pow(x, 3)), multiply(0.006, pow(x, 2))),
|
||||
multiply(0.36, x),
|
||||
2
|
||||
);
|
||||
}
|
||||
|
||||
function b3Friction3(x) {
|
||||
return add(
|
||||
sub(multiply(0.00000045, pow(x, 3)), multiply(0.000332, pow(x, 2))),
|
||||
multiply(0.1078, x),
|
||||
5.84
|
||||
);
|
||||
}
|
||||
|
||||
function b3Nobounce(tension) {
|
||||
return cond(
|
||||
lessOrEq(tension, 18),
|
||||
b3Friction1(tension),
|
||||
cond(
|
||||
and(greaterThan(tension, 18), lessOrEq(tension, 44)),
|
||||
b3Friction2(tension),
|
||||
b3Friction3(tension)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
let b = normalize(divide(bounciness, 1.7), 0, 20);
|
||||
b = projectNormal(b, 0, 0.8);
|
||||
let s = normalize(divide(speed, 1.7), 0, 20);
|
||||
let bouncyTension = projectNormal(s, 0.5, 200);
|
||||
let bouncyFriction = quadraticOutInterpolation(
|
||||
b,
|
||||
b3Nobounce(bouncyTension),
|
||||
0.01
|
||||
);
|
||||
return {
|
||||
...rest,
|
||||
stiffness: stiffnessFromOrigamiNode(bouncyTension),
|
||||
damping: dampingFromOrigamiNode(bouncyFriction),
|
||||
};
|
||||
}
|
||||
|
||||
function fromBouncinessAndSpeedNumbers(bounciness, speed, rest) {
|
||||
function normalize(value, startValue, endValue) {
|
||||
return (value - startValue) / (endValue - startValue);
|
||||
}
|
||||
|
||||
function projectNormal(n, start, end) {
|
||||
return start + n * (end - start);
|
||||
}
|
||||
|
||||
function linearInterpolation(t, start, end) {
|
||||
return t * end + (1 - t) * start;
|
||||
}
|
||||
|
||||
function quadraticOutInterpolation(t, start, end) {
|
||||
return linearInterpolation(2 * t - t * t, start, end);
|
||||
}
|
||||
|
||||
function b3Friction1(x) {
|
||||
return 0.0007 * Math.pow(x, 3) - 0.031 * Math.pow(x, 2) + 0.64 * x + 1.28;
|
||||
}
|
||||
|
||||
function b3Friction2(x) {
|
||||
return 0.000044 * Math.pow(x, 3) - 0.006 * Math.pow(x, 2) + 0.36 * x + 2;
|
||||
}
|
||||
|
||||
function b3Friction3(x) {
|
||||
return (
|
||||
0.00000045 * Math.pow(x, 3) -
|
||||
0.000332 * Math.pow(x, 2) +
|
||||
0.1078 * x +
|
||||
5.84
|
||||
);
|
||||
}
|
||||
|
||||
function b3Nobounce(tension) {
|
||||
if (tension <= 18) {
|
||||
return b3Friction1(tension);
|
||||
} else if (tension > 18 && tension <= 44) {
|
||||
return b3Friction2(tension);
|
||||
} else {
|
||||
return b3Friction3(tension);
|
||||
}
|
||||
}
|
||||
|
||||
let b = normalize(bounciness / 1.7, 0, 20);
|
||||
b = projectNormal(b, 0, 0.8);
|
||||
const s = normalize(speed / 1.7, 0, 20);
|
||||
const bouncyTension = projectNormal(s, 0.5, 200);
|
||||
const bouncyFriction = quadraticOutInterpolation(
|
||||
b,
|
||||
b3Nobounce(bouncyTension),
|
||||
0.01
|
||||
);
|
||||
|
||||
return {
|
||||
...rest,
|
||||
stiffness: stiffnessFromOrigamiValue(bouncyTension),
|
||||
damping: dampingFromOrigamiValue(bouncyFriction),
|
||||
};
|
||||
}
|
||||
|
||||
function makeDefaultConfig() {
|
||||
return {
|
||||
stiffness: new AnimatedValue(100),
|
||||
mass: new AnimatedValue(1),
|
||||
damping: new AnimatedValue(10),
|
||||
overshootClamping: false,
|
||||
restSpeedThreshold: 0.001,
|
||||
restDisplacementThreshold: 0.001,
|
||||
toValue: new AnimatedValue(0),
|
||||
};
|
||||
}
|
||||
|
||||
export default {
|
||||
makeDefaultConfig,
|
||||
makeConfigFromBouncinessAndSpeed,
|
||||
makeConfigFromOrigamiTensionAndFriction,
|
||||
};
|
||||
Reference in New Issue
Block a user