diff --git a/.npmignore b/.npmignore index 52aee57..d838da9 100644 --- a/.npmignore +++ b/.npmignore @@ -1,2 +1 @@ -demos/ examples/ diff --git a/README.md b/README.md index b69c428..3e1bb29 100755 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ # react-native-behavior -<img src="https://raw.githubusercontent.com/sonaye/react-native-behavior/master/demos/demo1.gif" width="400"> +<img src="https://raw.githubusercontent.com/sonaye/react-native-behavior/master/examples/demos/demo1.gif" width="400"> You define the behavior states of the component, and then animate between them. @@ -7,21 +7,23 @@ You define the behavior states of the component, and then animate between them. <Behavior ref={ref => (this.box = ref)} states={[ - { backgroundColor: 'gray', height: 100, width: 100 }, // state 0 + { backgroundColor: 'gray' }, // state 0 { backgroundColor: 'green' }, // state 1 - { height: 150 }, // state 2 - { opacity: 0.5 }, // state 3 - { rotate: '45deg' }, // state 4 + { opacity: 0.5 }, // state 2 + { rotate: '45deg' }, // state 3 ]} /> // .. -this.box.goTo(1); -this.box.play([2, 3, 4]); +this.box.goTo(1); // animates box's backgroundColor from gray to green +this.box.goTo(2); // animates the opacity of the -now- green box from 1 to 0.5 +this.box.goTo(3); // rotates the faded green box 45 degrees, starting from 0 + +this.box.goTo([1, 2, 3]); // plays a sequence of behavior states, colorize then fade then tilt ``` -More demos available [here](https://github.com/sonaye/react-native-behavior/tree/master/demos). +More demos available [here](https://github.com/sonaye/react-native-behavior/tree/master/examples/demos). # Installation `yarn add react-native-behavior` @@ -29,7 +31,7 @@ More demos available [here](https://github.com/sonaye/react-native-behavior/tree # Definition ```javascript type behavior = { - config?: { // goTo() and play() default configuration + config?: { // goTo() default configuration mode?: 'spring' | 'timing', // default = 'spring' callback?: Function, // to be executed after animating to a new state ...AnimatedSpringOptions, // excluding toValue, useNativeDriver (see React Native docs) @@ -46,25 +48,26 @@ type behavior = { translateY?: number, // default = 0 width?: number, // no percentages, default = null }>, // minimum two states required - style?: Object, // default = {} + style?: Object, // default = {}, AnimatedViewStyle (see React Native docs) enableGestures?: boolean, // simple swipe up/down/left/right and pressed/long pressed onGesture?: Function, // e.g. gesture => console.log(gesture) indices?: Array<number>, // required on android to avoid a glitch // indices can also be used with custom drivers to define custom state keys/values clamp?: boolean, // default = false, prevent animations from exceeding their ranges - swipeThreshold?: { velocity?: number, distance?: number }, // default = { velocity: 0.3, distance: 10 } + swipeVelocityThreshold?: number, // default = 0.3 + swipeDistanceThreshold?: number, // default = 10 animatedNativeValue?: AnimatedValue, // default = new Animated.Value(0), use a custom native driver - animatedValue?: AnimatedValue // default = new Animated.Value(0), use a custom driver + animatedValue?: AnimatedValue, // default = new Animated.Value(0), use a custom driver // animatedNativeValue and animatedValue should be used together, different instances of Animated.Value // animatedNativeValue is needed for opacity, rotate, scale, translateX and translateY // animatedValue is needed for backgroundColor, height and width + children?: any // the behavior component can enclose other components, can enclose another behavior too }; // methods -behavior.goTo(index: number, config?: Object = {}) // animate to a specific behavior state -behavior.play(indices: Array<number>, config?: Object = {}) // animate a sequence of behavior states +behavior.goTo(index: number | Array<number>, config?: Object = {}) // animate to a specific behavior state -// to retrieve current state index you can directly use behavior.index +behavior.index // to retrieve current state index ``` ## Examples diff --git a/behavior.js b/behavior.js index 6bb7409..38b3010 100644 --- a/behavior.js +++ b/behavior.js @@ -2,57 +2,52 @@ import React, { Component } from 'react'; import { Animated, PanResponder, TouchableOpacity } from 'react-native'; export default class extends Component { - index = this.props.initialState || 0; + static defaultProps = { + config: { mode: 'spring' }, + initialState: 0, + style: {}, + enableGestures: false, + clamp: false, + swipeVelocityThreshold: 0.3, + swipeDistanceThreshold: 10 + }; nativeValue = this.props.animatedNativeValue || new Animated.Value(0); value = this.props.animatedValue || new Animated.Value(0); - goTo = (toValue, config = {}) => { + index = this.props.initialState; + + goTo = (value, config = {}) => { const { mode, callback, ...options } = { ...this.props.config, ...config }; - const animate = mode === 'timing' ? Animated.timing : Animated.spring; + const curve = mode === 'timing' ? Animated.timing : Animated.spring; - Animated.parallel([ - animate(this.nativeValue, { ...options, toValue, useNativeDriver: true }), - animate(this.value, { ...options, toValue }) - ]).start(animation => { - if (animation.finished && callback) callback(); - }); + const animate = toValue => + Animated.parallel([ + curve(this.nativeValue, { ...options, toValue, useNativeDriver: true }), + curve(this.value, { ...options, toValue }) + ]); - this.index = toValue; - }; + if (Array.isArray(value)) { + const states = []; - play = (values, config = {}) => { - const { mode, callback, ...options } = { - ...this.props.config, - ...config - }; + value.forEach(toValue => states.push(animate(toValue))); - const animate = mode === 'timing' ? Animated.timing : Animated.spring; - - const states = []; - - values.forEach(toValue => - states.push( - Animated.parallel([ - animate(this.nativeValue, { - ...options, - toValue, - useNativeDriver: true - }), - animate(this.value, { ...options, toValue }) - ]) - ) - ); + Animated.sequence(states).start(animation => { + if (animation.finished && callback) callback(); + }); - Animated.sequence(states).start(animation => { - if (animation.finished && callback) callback(); - }); + this.index = states[states.length - 1]; + } else { + animate(value).start(animation => { + if (animation.finished && callback) callback(); + }); - this.index = values[values.length - 1]; + this.index = value; + } }; render() { @@ -66,25 +61,14 @@ export default class extends Component { initialState, onGesture, states, - swipeThreshold + style, + swipeVelocityThreshold, + swipeDistanceThreshold } = this.props; - const style = this.props.style || {}; - const inputRange = indices || [...Array(states.length).keys()]; - const defaultState = { - backgroundColor: 'transparent', - height: null, - opacity: 1, - rotate: '0deg', - scale: 1, - translateX: 0, - translateY: 0, - width: null - }; - - const getRange = prop => + const getRange = (prop, defaultValue) => states.reduce((range, state, i) => { const prevState = range[i - 1]; @@ -93,37 +77,35 @@ export default class extends Component { ? state[prop] : prevState || prevState === 0 ? prevState - : style[prop] || style[prop] === 0 - ? style[prop] - : defaultState[prop] + : style[prop] || style[prop] === 0 ? style[prop] : defaultValue ); return range; }, []); - const addNativeProp = prop => + const addNativeProp = (prop, defaultValue) => nativeValue.interpolate({ inputRange, - outputRange: getRange(prop), + outputRange: getRange(prop, defaultValue), extrapolate: clamp ? 'clamp' : null }); - const addProp = prop => + const addProp = (prop, defaultValue) => value.interpolate({ inputRange, - outputRange: getRange(prop), + outputRange: getRange(prop, defaultValue), extrapolate: clamp ? 'clamp' : null }); - const opacity = addNativeProp('opacity'); - const rotate = addNativeProp('rotate'); - const scale = addNativeProp('scale'); - const translateX = addNativeProp('translateX'); - const translateY = addNativeProp('translateY'); + const opacity = addNativeProp('opacity', 1); + const rotate = addNativeProp('rotate', '0deg'); + const scale = addNativeProp('scale', 1); + const translateX = addNativeProp('translateX', 0); + const translateY = addNativeProp('translateY', 0); - const backgroundColor = addProp('backgroundColor'); - const height = addProp('height'); - const width = addProp('width'); + const backgroundColor = addProp('backgroundColor', 'transparent'); + const height = addProp('height', null); + const width = addProp('width', null); const nativeStyles = { opacity, @@ -142,19 +124,22 @@ export default class extends Component { let swipeVelocity = null; let swipeDistance = null; - const velocityThr = (swipeThreshold && swipeThreshold.velocity) || 0.3; - const distanceThr = (swipeThreshold && swipeThreshold.distance) || 10; - this.pan = PanResponder.create({ onMoveShouldSetPanResponder: e => e.nativeEvent.touches.length === 1, onPanResponderMove: (e, { dx, dy, vx, vy }) => { - if (Math.abs(vx) > velocityThr && Math.abs(dy) < distanceThr) { + if ( + Math.abs(vx) > swipeVelocityThreshold && + Math.abs(dy) < swipeDistanceThreshold + ) { swipeVelocity = vx; swipeDistance = dx; if (dx < 0) swiped = 'left'; else if (dx > 0) swiped = 'right'; - } else if (Math.abs(vy) > velocityThr && Math.abs(dx) < distanceThr) { + } else if ( + Math.abs(vy) > swipeVelocityThreshold && + Math.abs(dx) < swipeDistanceThreshold + ) { swipeVelocity = vy; swipeDistance = dy; diff --git a/examples/box.js b/examples/box.js index fa19c02..c989886 100644 --- a/examples/box.js +++ b/examples/box.js @@ -5,17 +5,13 @@ import Behavior from '../behavior'; const Example = () => { let goTo; - let play; return ( <View style={{ flex: 1 }}> <View style={{ alignItems: 'center', flex: 1, justifyContent: 'center' }}> <Behavior config={{ mode: 'timing', duration: 250 }} - ref={ref => { - goTo = ref.goTo; - play = ref.play; - }} + ref={ref => (goTo = ref.goTo)} states={[ { backgroundColor: '#d8d8d8', height: 100, width: 100 }, { backgroundColor: '#0f9d58' }, @@ -57,12 +53,12 @@ const Example = () => { <Button color="#d8d8d8" title="play" - onPress={() => play([1, 2, 3, 4, 5, 6, 7, 8])} + onPress={() => goTo([1, 2, 3, 4, 5, 6, 7, 8])} /> <Button color="#d8d8d8" title="reset" - onPress={() => play([7, 6, 5, 4, 3, 2, 1, 0])} + onPress={() => goTo([7, 6, 5, 4, 3, 2, 1, 0])} /> </View> </View> diff --git a/examples/containerWithGestures.js b/examples/containerWithGestures.js index 1370308..3eff8eb 100644 --- a/examples/containerWithGestures.js +++ b/examples/containerWithGestures.js @@ -29,7 +29,7 @@ const Example = () => else this.container.goTo(0); }} indices={[0, 1, 2]} // android only - swipeThreshold={{ distance: 40 }} + swipeDistanceThreshold={40} /> </View>; diff --git a/demos/README.md b/examples/demos/README.md similarity index 100% rename from demos/README.md rename to examples/demos/README.md diff --git a/demos/demo1.gif b/examples/demos/demo1.gif similarity index 100% rename from demos/demo1.gif rename to examples/demos/demo1.gif diff --git a/demos/demo2.gif b/examples/demos/demo2.gif similarity index 100% rename from demos/demo2.gif rename to examples/demos/demo2.gif diff --git a/demos/demo3.gif b/examples/demos/demo3.gif similarity index 100% rename from demos/demo3.gif rename to examples/demos/demo3.gif diff --git a/demos/demo4.gif b/examples/demos/demo4.gif similarity index 100% rename from demos/demo4.gif rename to examples/demos/demo4.gif diff --git a/demos/demo5.gif b/examples/demos/demo5.gif similarity index 100% rename from demos/demo5.gif rename to examples/demos/demo5.gif diff --git a/demos/demo6.gif b/examples/demos/demo6.gif similarity index 100% rename from demos/demo6.gif rename to examples/demos/demo6.gif diff --git a/demos/demo7.gif b/examples/demos/demo7.gif similarity index 100% rename from demos/demo7.gif rename to examples/demos/demo7.gif diff --git a/demos/demo8.gif b/examples/demos/demo8.gif similarity index 100% rename from demos/demo8.gif rename to examples/demos/demo8.gif diff --git a/demos/demo9.gif b/examples/demos/demo9.gif similarity index 100% rename from demos/demo9.gif rename to examples/demos/demo9.gif diff --git a/examples/sequence.js b/examples/sequence.js index af43b7d..e64eb32 100644 --- a/examples/sequence.js +++ b/examples/sequence.js @@ -14,7 +14,7 @@ const Example = () => { config={{ mode: 'timing', delay: i * 250 }} indices={[0, 1, 2, 3, 4]} key={i} - ref={ref => ref.play([1, 2, 3, 4])} + ref={ref => ref.goTo([1, 2, 3, 4])} states={[ { translateX: -width, opacity: 0 }, { translateX: 0, opacity: 1 }, diff --git a/package.json b/package.json index 76eaa2d..a9a6893 100755 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "react-native-behavior", "description": "Easily create stateful animations in React Native.", - "version": "0.0.17", + "version": "0.0.18", "main": "behavior.js", "repository": { "type": "git",