diff --git a/Example/App.js b/Example/App.js index 2356be36b13f..57669322e09b 100644 --- a/Example/App.js +++ b/Example/App.js @@ -1,11 +1,12 @@ import React from 'react'; import { Text, View, FlatList, StyleSheet, YellowBox } from 'react-native'; -import { StackNavigator } from 'react-navigation'; +import { createStackNavigator } from 'react-navigation'; import { RectButton, ScrollView } from 'react-native-gesture-handler'; import Snappable from './snappable'; import ImageViewer from './imageViewer'; import Test from './test'; +import Interpolate from './src/interpolate'; YellowBox.ignoreWarnings([ 'Warning: isMounted(...) is deprecated', @@ -18,11 +19,12 @@ const SCREENS = { Snappable: { screen: Snappable, title: 'Snappable' }, Test: { screen: Test, title: 'Test' }, ImageViewer: { screen: ImageViewer, title: 'Image Viewer' }, + Interpolate: { screen: Interpolate, title: 'Interpolate' }, }; class MainScreen extends React.Component { static navigationOptions = { - title: '🎬 Reanimated Demo', + title: '🎬 Reanimated Examples', }; render() { const data = Object.keys(SCREENS).map(key => ({ key })); @@ -57,7 +59,7 @@ class MainScreenItem extends React.Component { } } -const ExampleApp = StackNavigator( +const ExampleApp = createStackNavigator( { Main: { screen: MainScreen }, ...SCREENS, diff --git a/Example/imageViewer/index.js b/Example/imageViewer/index.js index 78ec369e1fc0..84a6c8671420 100644 --- a/Example/imageViewer/index.js +++ b/Example/imageViewer/index.js @@ -420,6 +420,9 @@ class Viewer extends Component { } export default class Example extends Component { + static navigationOptions = { + title: 'Image Viewer Example', + }; render() { return ( diff --git a/Example/package.json b/Example/package.json index 3200cc30f0dd..858c0c70c992 100644 --- a/Example/package.json +++ b/Example/package.json @@ -12,7 +12,7 @@ "react-native": "0.55.3", "react-native-gesture-handler": "^1.0.0", "react-native-reanimated": "file:../", - "react-navigation": "^1.5.11" + "react-navigation": "^2.0.4" }, "devDependencies": { "babel-jest": "22.4.3", diff --git a/Example/snappable/index.js b/Example/snappable/index.js index e21d6d5f813c..481a0c8ed1a2 100644 --- a/Example/snappable/index.js +++ b/Example/snappable/index.js @@ -128,6 +128,9 @@ class Snappable extends Component { } export default class Example extends Component { + static navigationOptions = { + title: 'Snappable Example', + }; render() { return ( diff --git a/Example/src/Box.js b/Example/src/Box.js new file mode 100644 index 000000000000..479a66a9c85d --- /dev/null +++ b/Example/src/Box.js @@ -0,0 +1,25 @@ +import React from 'react'; +import { StyleSheet } from 'react-native'; +import Animated, { Easing } from 'react-native-reanimated'; + +/** + * Needs to be a class component for react-native-gesture-handler to put a ref on it. + */ +export default class Box extends React.Component { + render() { + const { style, ...props } = this.props; + return ; + } +} + +const BOX_SIZE = 44; + +const styles = StyleSheet.create({ + box: { + width: BOX_SIZE, + height: BOX_SIZE, + alignSelf: 'center', + backgroundColor: 'blue', + margin: 10, + }, +}); diff --git a/Example/src/Row.js b/Example/src/Row.js new file mode 100644 index 000000000000..33b7ca6f2a61 --- /dev/null +++ b/Example/src/Row.js @@ -0,0 +1,14 @@ +import React from 'react'; +import { View, StyleSheet } from 'react-native'; + +const Row = ({ style, ...props }) => ( + +); + +const styles = StyleSheet.create({ + style: { + height: 64, + }, +}); + +export default Row; diff --git a/Example/src/interpolate/AnimatedBounds.js b/Example/src/interpolate/AnimatedBounds.js new file mode 100644 index 000000000000..004757e1ecb6 --- /dev/null +++ b/Example/src/interpolate/AnimatedBounds.js @@ -0,0 +1,233 @@ +import React, { Component } from 'react'; +import { StyleSheet, View } from 'react-native'; +import Animated, { Easing } from 'react-native-reanimated'; +import { PanGestureHandler, State } from 'react-native-gesture-handler'; +import Box from '../Box'; +import Row from '../Row'; + +const { + set, + cond, + sub, + eq, + and, + add, + call, + multiply, + lessThan, + startClock, + stopClock, + clockRunning, + block, + timing, + debug, + spring, + Value, + Clock, + event, + interpolate, + defined, +} = Animated; + +function runSpring(clock, value, velocity, dest) { + const state = { + finished: new Value(0), + velocity: new Value(0), + position: new Value(0), + time: new Value(0), + }; + + const config = { + damping: 7, + mass: 1, + stiffness: 121.6, + overshootClamping: false, + restSpeedThreshold: 0.001, + restDisplacementThreshold: 0.001, + toValue: new Value(0), + }; + + return [ + cond(clockRunning(clock), 0, [ + set(state.finished, 0), + set(state.velocity, velocity), + set(state.position, value), + set(config.toValue, dest), + startClock(clock), + ]), + cond(state.finished, stopClock(clock)), + state.position, + ]; +} + +function runTiming(clock, value, dest) { + const state = { + finished: new Value(1), + position: new Value(value), + time: new Value(0), + frameTime: new Value(0), + }; + + const config = { + duration: 500, + toValue: new Value(0), + easing: Easing.inOut(Easing.ease), + }; + + const reset = [ + set(state.finished, 0), + set(state.time, 0), + set(state.frameTime, 0), + ]; + + return block([ + cond(and(state.finished, eq(state.position, value)), [ + ...reset, + set(config.toValue, dest), + ]), + cond(and(state.finished, eq(state.position, dest)), [ + ...reset, + set(config.toValue, value), + ]), + cond(clockRunning(clock), 0, startClock(clock)), + timing(clock, state, config), + state.position, + ]); +} + +const getAnimation = (min, max) => { + const clock = new Clock(); + const state = { + finished: new Value(1), + position: new Value(min), + time: new Value(0), + frameTime: new Value(0), + }; + + const config = { + duration: 500, + toValue: new Value(0), + easing: Easing.inOut(Easing.ease), + }; + + const reset = [ + set(state.finished, 0), + set(state.time, 0), + set(state.frameTime, 0), + ]; + + return block([ + cond(and(state.finished, eq(state.position, min)), [ + ...reset, + set(config.toValue, max), + ]), + cond(and(state.finished, eq(state.position, max)), [ + ...reset, + set(config.toValue, min), + ]), + cond(clockRunning(clock), 0, startClock(clock)), + timing(clock, state, config), + state.position, + ]); +}; + +export default class AnimatedBounds extends Component { + constructor(props) { + super(props); + const TOSS_SEC = 0.2; + + const dragX = new Value(0); + const state = new Value(-1); + const dragVX = new Value(0); + const transX = new Value(); + const prevDragX = new Value(0); + const clock = new Clock(); + + this._onGestureEvent = event([ + { nativeEvent: { translationX: dragX, velocityX: dragVX, state: state } }, + ]); + + const snapPoint = cond( + lessThan(add(transX, multiply(TOSS_SEC, dragVX)), 0), + -100, + 100 + ); + + this._transX = cond( + eq(state, State.ACTIVE), + [ + stopClock(clock), + set(transX, add(transX, sub(dragX, prevDragX))), + set(prevDragX, dragX), + transX, + ], + [ + set(prevDragX, 0), + set( + transX, + cond(defined(transX), runSpring(clock, transX, dragVX, snapPoint), 0) + ), + ] + ); + + this._transX = interpolate(this._transX, { + inputRange: [-100, 100], + outputRange: [-100, 100], + extrapolate: 'clamp', + }); + + const min = getAnimation(-100, -50); + const max = getAnimation(100, 50); + this._transXA = interpolate(this._transX, { + inputRange: [-100, 100], + outputRange: [min, max], + extrapolate: 'clamp', + }); + this.min = min; + this.max = max; + } + render() { + return ( + + + + + + + + + + + + + + + ); + } +} + +const styles = StyleSheet.create({ + container: { + flex: 1, + justifyContent: 'center', + alignItems: 'center', + }, + line: { + position: 'absolute', + alignSelf: 'center', + backgroundColor: 'red', + height: 64, + width: 1, + }, +}); diff --git a/Example/src/interpolate/Basic.js b/Example/src/interpolate/Basic.js new file mode 100644 index 000000000000..3111fe2de300 --- /dev/null +++ b/Example/src/interpolate/Basic.js @@ -0,0 +1,81 @@ +import React, { Component } from 'react'; +import { StyleSheet, View } from 'react-native'; +import Animated, { Easing } from 'react-native-reanimated'; +import Box from '../Box'; +import Row from '../Row'; + +const { + set, + cond, + eq, + and, + add, + call, + multiply, + lessThan, + startClock, + stopClock, + clockRunning, + block, + timing, + debug, + spring, + Value, + Clock, + event, + interpolate, +} = Animated; + +function runTiming(clock, value, dest) { + const state = { + finished: new Value(1), + position: new Value(value), + time: new Value(0), + frameTime: new Value(0), + }; + + const config = { + duration: 500, + toValue: new Value(0), + easing: Easing.inOut(Easing.ease), + }; + + const reset = [ + set(state.finished, 0), + set(state.time, 0), + set(state.frameTime, 0), + ]; + + return block([ + cond(and(state.finished, eq(state.position, value)), [ + ...reset, + set(config.toValue, dest), + ]), + cond(and(state.finished, eq(state.position, dest)), [ + ...reset, + set(config.toValue, value), + ]), + cond(clockRunning(clock), 0, startClock(clock)), + timing(clock, state, config), + state.position, + ]); +} + +export default class Basic extends Component { + constructor(props) { + super(props); + const clock = new Clock(); + const base = runTiming(clock, -1, 1); + this._transX = interpolate(base, { + inputRange: [-1, 1], + outputRange: [-100, 100], + }); + } + render() { + return ( + + + + ); + } +} diff --git a/Example/src/interpolate/WithDrag.js b/Example/src/interpolate/WithDrag.js new file mode 100644 index 000000000000..ff8b4a2684d5 --- /dev/null +++ b/Example/src/interpolate/WithDrag.js @@ -0,0 +1,176 @@ +import React, { Component } from 'react'; +import { StyleSheet, View } from 'react-native'; +import Animated, { Easing } from 'react-native-reanimated'; +import { PanGestureHandler, State } from 'react-native-gesture-handler'; +import Box from '../Box'; +import Row from '../Row'; + +const { + set, + cond, + sub, + eq, + and, + add, + call, + multiply, + lessThan, + startClock, + stopClock, + clockRunning, + block, + timing, + debug, + spring, + Value, + Clock, + event, + interpolate, + defined, +} = Animated; + +function runSpring(clock, value, velocity, dest) { + const state = { + finished: new Value(0), + velocity: new Value(0), + position: new Value(0), + time: new Value(0), + }; + + const config = { + damping: 7, + mass: 1, + stiffness: 121.6, + overshootClamping: false, + restSpeedThreshold: 0.001, + restDisplacementThreshold: 0.001, + toValue: new Value(0), + }; + + return [ + cond(clockRunning(clock), 0, [ + set(state.finished, 0), + set(state.velocity, velocity), + set(state.position, value), + set(config.toValue, dest), + startClock(clock), + ]), + cond(state.finished, stopClock(clock)), + state.position, + ]; +} + +function runTiming(clock, value, dest) { + const state = { + finished: new Value(1), + position: new Value(value), + time: new Value(0), + frameTime: new Value(0), + }; + + const config = { + duration: 500, + toValue: new Value(0), + easing: Easing.inOut(Easing.ease), + }; + + const reset = [ + set(state.finished, 0), + set(state.time, 0), + set(state.frameTime, 0), + ]; + + return block([ + cond(and(state.finished, eq(state.position, value)), [ + ...reset, + set(config.toValue, dest), + ]), + cond(and(state.finished, eq(state.position, dest)), [ + ...reset, + set(config.toValue, value), + ]), + cond(clockRunning(clock), 0, startClock(clock)), + timing(clock, state, config), + state.position, + ]); +} + +export default class WithDrag extends Component { + constructor(props) { + super(props); + const TOSS_SEC = 0.2; + + const dragX = new Value(0); + const state = new Value(-1); + const dragVX = new Value(0); + const transX = new Value(); + const prevDragX = new Value(0); + const clock = new Clock(); + + this._onGestureEvent = event([ + { nativeEvent: { translationX: dragX, velocityX: dragVX, state: state } }, + ]); + + const snapPoint = cond( + lessThan(add(transX, multiply(TOSS_SEC, dragVX)), 0), + -100, + 100 + ); + + this._transX = cond( + eq(state, State.ACTIVE), + [ + stopClock(clock), + set(transX, add(transX, sub(dragX, prevDragX))), + set(prevDragX, dragX), + transX, + ], + [ + set(prevDragX, 0), + set( + transX, + cond(defined(transX), runSpring(clock, transX, dragVX, snapPoint), 0) + ), + ] + ); + + this._transXA = interpolate(this._transX, { + inputRange: [-120, 120], + outputRange: [-100, 100], + }); + this._transXB = interpolate(this._transX, { + inputRange: [-120, -60, 60, 120], + outputRange: [-60, -10, 10, 60], + }); + } + render() { + return ( + + + + + + + + + + + + + + ); + } +} + +const BOX_SIZE = 44; + +const styles = StyleSheet.create({ + container: { + flex: 1, + justifyContent: 'center', + alignItems: 'center', + }, +}); diff --git a/Example/src/interpolate/index.js b/Example/src/interpolate/index.js new file mode 100644 index 000000000000..de909e46dfea --- /dev/null +++ b/Example/src/interpolate/index.js @@ -0,0 +1,69 @@ +import React from 'react'; +import { Text, View, FlatList, StyleSheet, YellowBox } from 'react-native'; +import { StackNavigator } from 'react-navigation'; +import { RectButton, ScrollView } from 'react-native-gesture-handler'; + +import Basic from './Basic'; +import WithDrag from './WithDrag'; +import AnimatedBounds from './AnimatedBounds'; + +const examples = [Basic, WithDrag, AnimatedBounds].map(v => ({ + key: v.displayName, + title: v.displayName, + Component: v, +})); + +class Item extends React.Component { + render() { + const item = this.props.item; + const Comp = this.props.item.Component; + + return ( + + {item.title} + + + ); + } +} + +class MainScreen extends React.Component { + static navigationOptions = { + title: 'Interpolation Examples', + }; + renderItem = props => ; + render() { + return ( + + ); + } +} + +const ItemSeparator = () => ; + +const styles = StyleSheet.create({ + list: { + backgroundColor: '#EFEFF4', + }, + separator: { + height: 1, + backgroundColor: '#DBDBE0', + }, + buttonText: { + backgroundColor: 'transparent', + }, + button: { + flex: 1, + flexDirection: 'column', + alignItems: 'center', + backgroundColor: '#fff', + padding: 20, + }, +}); + +export default MainScreen; diff --git a/Example/yarn.lock b/Example/yarn.lock index 275e0f02a679..fe971965a72c 100644 --- a/Example/yarn.lock +++ b/Example/yarn.lock @@ -1657,6 +1657,13 @@ create-react-class@^15.6.3: loose-envify "^1.3.1" object-assign "^4.1.1" +create-react-context@^0.2.1: + version "0.2.2" + resolved "https://registry.yarnpkg.com/create-react-context/-/create-react-context-0.2.2.tgz#9836542f9aaa22868cd7d4a6f82667df38019dca" + dependencies: + fbjs "^0.8.0" + gud "^1.0.0" + cross-spawn@^5.0.1, cross-spawn@^5.1.0: version "5.1.0" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-5.1.0.tgz#e8bd0efee58fcff6f8f94510a0a554bbfa235449" @@ -2089,7 +2096,7 @@ fbjs-scripts@^0.8.1: semver "^5.1.0" through2 "^2.0.0" -fbjs@^0.8.14, fbjs@^0.8.16, fbjs@^0.8.9: +fbjs@^0.8.0, fbjs@^0.8.14, fbjs@^0.8.16, fbjs@^0.8.9: version "0.8.16" resolved "https://registry.yarnpkg.com/fbjs/-/fbjs-0.8.16.tgz#5e67432f550dc41b572bf55847b8aca64e5337db" dependencies: @@ -2323,6 +2330,10 @@ growly@^1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/growly/-/growly-1.3.0.tgz#f10748cbe76af964b7c96c93c6bcc28af120c081" +gud@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/gud/-/gud-1.0.0.tgz#a489581b17e6a70beca9abe3ae57de7a499852c0" + handlebars@^4.0.3: version "4.0.11" resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.0.11.tgz#630a35dfe0294bc281edae6ffc5d329fc7982dcc" @@ -2408,7 +2419,7 @@ hoek@4.x.x: version "4.2.1" resolved "https://registry.yarnpkg.com/hoek/-/hoek-4.2.1.tgz#9634502aa12c445dd5a7c5734b572bb8738aacbb" -hoist-non-react-statics@^2.2.0, hoist-non-react-statics@^2.3.1: +hoist-non-react-statics@^2.2.0, hoist-non-react-statics@^2.3.1, hoist-non-react-statics@^2.5.0: version "2.5.0" resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-2.5.0.tgz#d2ca2dfc19c5a91c5a6615ce8e564ef0347e2a40" @@ -4160,9 +4171,9 @@ react-is@^16.3.1: version "16.3.2" resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.3.2.tgz#f4d3d0e2f5fbb6ac46450641eb2e25bf05d36b22" -react-lifecycles-compat@^1.0.2: - version "1.1.4" - resolved "https://registry.yarnpkg.com/react-lifecycles-compat/-/react-lifecycles-compat-1.1.4.tgz#fc005c72849b7ed364de20a0f64ff58ebdc2009a" +react-lifecycles-compat@^3, react-lifecycles-compat@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz#4f1a273afdfc8f3488a8c516bfda78f872352362" react-native-dismiss-keyboard@1.0.0: version "1.0.0" @@ -4197,9 +4208,21 @@ react-native-safe-area-view@^0.7.0: dependencies: hoist-non-react-statics "^2.3.1" -"react-native-tab-view@github:react-navigation/react-native-tab-view": - version "0.0.74" - resolved "https://codeload.github.com/react-navigation/react-native-tab-view/tar.gz/36ebd834d78b841fc19778c966465d02fd1213bb" +react-native-safe-area-view@^0.8.0: + version "0.8.0" + resolved "https://registry.yarnpkg.com/react-native-safe-area-view/-/react-native-safe-area-view-0.8.0.tgz#22d78cb8e8658d04a10cd53c1546e0bc86cb7aea" + dependencies: + hoist-non-react-statics "^2.3.1" + +react-native-tab-view@^0.0.77: + version "0.0.77" + resolved "https://registry.yarnpkg.com/react-native-tab-view/-/react-native-tab-view-0.0.77.tgz#11ceb8e7c23100d07e628dc151b57797524d00d4" + dependencies: + prop-types "^15.6.0" + +react-native-tab-view@~0.0.78: + version "0.0.78" + resolved "https://registry.yarnpkg.com/react-native-tab-view/-/react-native-tab-view-0.0.78.tgz#9b90730d89cbd34a03f0e0ab10e74ca7af945560" dependencies: prop-types "^15.6.0" @@ -4267,18 +4290,36 @@ react-native@0.55.3: xmldoc "^0.4.0" yargs "^9.0.0" -react-navigation@^1.5.11: - version "1.5.11" - resolved "https://registry.yarnpkg.com/react-navigation/-/react-navigation-1.5.11.tgz#fba6ab45d7b9987c1763a5aaac53b4f6d62b7f5c" +react-navigation-deprecated-tab-navigator@1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/react-navigation-deprecated-tab-navigator/-/react-navigation-deprecated-tab-navigator-1.3.0.tgz#015dcae1e977b984ca7e99245261c15439026bb7" + dependencies: + react-native-tab-view "^0.0.77" + +react-navigation-tabs@0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/react-navigation-tabs/-/react-navigation-tabs-0.3.0.tgz#b1fe7ef1c665dd8928fafcc8622616e220ae5efa" + dependencies: + hoist-non-react-statics "^2.5.0" + prop-types "^15.6.0" + react-lifecycles-compat "^3.0.4" + react-native-safe-area-view "^0.7.0" + react-native-tab-view "~0.0.78" + +react-navigation@^2.0.4: + version "2.0.4" + resolved "https://registry.yarnpkg.com/react-navigation/-/react-navigation-2.0.4.tgz#efc09de09799c2117a54002a6ea0e9b80e92828d" dependencies: clamp "^1.0.1" + create-react-context "^0.2.1" hoist-non-react-statics "^2.2.0" path-to-regexp "^1.7.0" prop-types "^15.5.10" - react-lifecycles-compat "^1.0.2" + react-lifecycles-compat "^3" react-native-drawer-layout-polyfill "^1.3.2" - react-native-safe-area-view "^0.7.0" - react-native-tab-view "github:react-navigation/react-native-tab-view" + react-native-safe-area-view "^0.8.0" + react-navigation-deprecated-tab-navigator "1.3.0" + react-navigation-tabs "0.3.0" react-proxy@^1.1.7: version "1.1.8" diff --git a/README.md b/README.md index 824737adf202..98e0f403903d 100644 --- a/README.md +++ b/README.md @@ -70,7 +70,6 @@ All the functionality that missing elements provide in Animated can be already a - [ ] value tracking (can be achieved in different way, reanimated also allows for tracking all the animation parameters not only destination params) - [ ] animation delays - [ ] animation staggering - - [ ] interpolate method ## Clocks @@ -422,6 +421,29 @@ Returns an accumulated value of the given node. This node stores a sum of all ev Works the same way as with the original Animated library. +--- +### `interpolate` +```js +interpolate(node, { + // Input range for the interpolation. Should be monotonically increasing. + inputRange: [nodeOrValue...], + // Output range for the interpolation, should be the same length as the input range. + outputRange: [nodeOrValue...], + // Sets the left and right extrapolate modes. + extrapolate?: Extrapolate.EXTEND | Extrapolate.CLAMP | Extrapolate.IDENTITY, + // Set the left extrapolate mode, the behavior if the input is less than the first value in inputRange. + extrapolateLeft?: Extrapolate.EXTEND | Extrapolate.CLAMP | Extrapolate.IDENTITY, + // Set the right extrapolate mode, the behavior if the input is greater than the last value in inputRange. + extrapolateRight?: Extrapolate.EXTEND | Extrapolate.CLAMP | Extrapolate.IDENTITY, +}) + +Extrapolate.EXTEND; // Will extend the range linearly. +Extrapolate.CLAMP; // Will clamp the input value to the range. +Extrapolate.IDENTITY; // Will return the input value if the input value is out of range. +``` + +Maps an input value within a range to an output value within a range. Also supports different types of extrapolation for when the value falls outside the range. + --- @@ -434,7 +456,7 @@ decay(clock, { finished, velocity, position, time }, { deceleration }) Updates `position` and `velocity` nodes by running a single step of animation each time this node evaluates. State variable `finished` is set to `1` when the animation gets to the final point (that is the velocity drops under the level of significance). The `time` state node is populated automatically by this node and refers to the last clock time this node got evaluated. It is expected to be reset each time we want to restart the animation. Decay animation can be configured using `deceleration` config param and it controls how fast the animation decelerates. The value should be between `0` and `1` but only values that are close to `1` would yield meaningful results. --- -#### `timing` +### `timing` ```js timing(clock, { finished, position, frameTime, time }, { toValue, duration, easing }) @@ -444,7 +466,7 @@ Updates `position` node by running timing based animation from a given position The `frameTime` node will also get updated and represents the progress of animation in milliseconds (how long the animation has lasted so far). Similarly to the `time` node that just indicates the last clock time the animation node has been evaluated. Both of these variables are expected to be reset before restarting the animation. Finally `finished` node will be set to `1` when the position reaches the final value or when `frameTime` exceeds `duration`. --- -#### `spring` +### `spring` ```js spring(clock, { finished, position, velocity, time }, { damping, mass, stiffness, overshootClamping, restSpeedThreshold, restDisplacementThreshold, toValue }) diff --git a/src/derived.js b/src/derived.js index 6981bf27076c..1bc423f704e0 100644 --- a/src/derived.js +++ b/src/derived.js @@ -7,13 +7,14 @@ import { sub, set, add, + divide, } from './base'; import AnimatedValue from './core/AnimatedValue'; import { adapt } from './utils'; -export function abs(a) { +export const abs = function(a) { return cond(lessThan(a, 0), multiply(-1, a), a); -} +}; export const min = function(a, b) { a = adapt(a); @@ -49,3 +50,76 @@ export const diffClamp = function(a, minVal, maxVal) { min(max(add(cond(defined(value), value, a), diff(a)), minVal), maxVal) ); }; + +const interpolateInternalSingle = function( + value, + inputRange, + outputRange, + offset +) { + const inS = inputRange[offset]; + const inE = inputRange[offset + 1]; + const outS = outputRange[offset]; + const outE = outputRange[offset + 1]; + const progress = divide(sub(value, inS), sub(inE, inS)); + return add(outS, multiply(progress, sub(outE, outS))); +}; + +const interpolateInternal = function( + value, + inputRange, + outputRange, + offset = 0 +) { + if (inputRange.length - offset === 2) { + return interpolateInternalSingle(value, inputRange, outputRange, offset); + } + return cond( + lessThan(value, inputRange[offset + 1]), + interpolateInternalSingle(value, inputRange, outputRange, offset), + interpolateInternal(value, inputRange, outputRange, offset + 1) + ); +}; + +export const Extrapolate = { + EXTEND: 'EXTEND', + CLAMP: 'CLAMP', + IDENTITY: 'IDENTITY', +}; + +export const interpolate = function(value, config) { + const { + inputRange, + outputRange, + extrapolate = Extrapolate.EXTEND, + extrapolateLeft, + extrapolateRight, + } = config; + const left = extrapolateLeft || extrapolate; + const right = extrapolateRight || extrapolate; + let output = interpolateInternal(value, inputRange, outputRange); + + if (left === Extrapolate.EXTEND) { + } else if (left === Extrapolate.CLAMP) { + output = cond(lessThan(value, inputRange[0]), outputRange[0], output); + } else if (left === Extrapolate.IDENTITY) { + output = cond(lessThan(value, inputRange[0]), value, output); + } + + if (right === Extrapolate.EXTEND) { + } else if (right === Extrapolate.CLAMP) { + output = cond( + greaterThan(value, inputRange[inputRange.length - 1]), + outputRange[outputRange.length - 1], + output + ); + } else if (right === Extrapolate.IDENTITY) { + output = cond( + greaterThan(value, inputRange[inputRange.length - 1]), + value, + output + ); + } + + return output; +};