Skip to content

Instantly share code, notes, and snippets.

@conrad-vanl
Last active July 21, 2016 20:46
Show Gist options
  • Save conrad-vanl/eab370b00314401c6c4ebfa0453c9d6f to your computer and use it in GitHub Desktop.
Save conrad-vanl/eab370b00314401c6c4ebfa0453c9d6f to your computer and use it in GitHub Desktop.
Differential Sharing n' Caring: Intro to React-Native Animated library and Springs
// Imports
import React, { Component } from 'react';
import {
Animated,
AppRegistry,
StyleSheet,
Text,
View,
Dimensions,
TouchableHighlight,
} from 'react-native';
// How is our spring going to behave?
// There are several parameters we can use to customize
// the behavior of a spring. Generally, you might have only a few
// different spring configurations for an entire app - so that
// your animations remain consistent in feel from one part to another.
const springConfig = {
tension: 7, // How tightly wound the spring is - how much force does the spring start with when released?
friction: 4.5, // The amount of force acting in the opposite direction of the spring movement
};
// Here are all of the options: http://browniefed.com/react-native-animation-book/api/SPRING.html
class AnimatedDemo extends Component {
render() {
return (
<View style={styles.container}>
<OrbButton />
</View>
);
}
};
class OrbButton extends Component {
constructor() {
super();
// First: Create our primary Animated.Value()
// You can think of this as the "timeline" of the animation, it will control
// all of the properties we're animating (by using interpolation later on).
// We'll only ever animate this value to 0 or 1...
// 1 represents the end position of the animation, 0 the beginning.
const position = new Animated.Value(0);
// We need to render out 3 "orbs" to animate. For the simplicity
// of this demo, lets create an array that we'll put in 3 Animated.Value's:
let orbPositions = [];
// We also want each of these "orbs" to follow one other instead of them animating at the same time...
// Imagine a spring connecting each of the orbs together:
// (orb)-==spring==-(orb)-==spring==-(orb)-==spring==
// ^ pulling this spring causes a chain reaction
// lets push the first Animated.Value into the array:
orbPositions.push(position);
// Then create 2 more that are "linked" via a Spring to the previous Animated.Value:
for (let i = 0; i < 2; i++) {
let animatedValue = new Animated.Value(0);
Animated.spring(animatedValue, { toValue: orbPositions[i], ...springConfig }).start();
// ^-- this works because when you call `.start()` on a Spring that references another Animated.Value,
// it will observe that value and update the spring when it changes
orbPositions.push(animatedValue);
}
// Note: while we are here, you do not have to attach these to state - Animations do not trigger
// re-renders and we o not call this.setState(...) to update the position of these values.
// The only time you need to re-render when dealing with Animated.Value is if you want to change the
// way a Animated.Value behaves
this.state = { position, orbPositions }
}
render() {
return (
<View style={styles.openerButton}>
{/*
We actually want to reverse the order of Orb positions so that the orb on top
is the leader, and the last orb rendered out is the follower.
There's a few different ways you could do this - such as the way your logic works in the
constructor. For the simplicity of this demo, I'm just going to copy the array and reverse right here.
If you take out `.slice().reverse()`, the orb on bottom (the one that turns red) will simply be the leader.
*/}
{this.state.orbPositions.slice().reverse().map((position, i) => (
<Animated.View
{...{/* The only difference between a <View/> and <Animated.View/> is that the latter accepts Animated.Value's in style*/}}
style={[styles.openerOrbButton, this.getAnimatedStyles(position, i)]}
key={i}
/>
))}
<TouchableHighlight style={styles.openerButtonTouchable} onPress={() => this.animate()}>
<View />
</TouchableHighlight>
</View>
);
}
// Define and set up your animated properties for each 'orb'
// `position` points to the Animated.Value for this orb,
// `i` is the index of this orb
getAnimatedStyles(position, i) {
// position can be animated from 0 to 1, and back to 0 again:
const inputRange = [ 0, 1 ];
// todo: don't use arbitrary values....
// we just need these to setup our positioning for the orbs
const initialOffset = 70;
const eachOffset = 50;
// Interpolation works by setting an inputRange and an outputRange.
// In this case, we want the translateY offset of our orb to start at 0,
// and end at a position that's based on that orb's position in the list of orbs
const translateY = position.interpolate({
inputRange,
outputRange: [ 0, -(eachOffset * i + initialOffset) ]
});
// You can also interpolate colors.
// This example makes the first orb change to red, the second to green, and the third to blue:
let finishedColor = [0, 0, 0];
finishedColor[i] = 255;
const backgroundColor = position.interpolate({
inputRange,
outputRange: [ 'rgb(255,255,255)', `rgb(${finishedColor})` ],
});
// Lastly, we just return what essentially is a style object:
return {
transform: [
{ translateY }
],
backgroundColor
}
}
animate() {
// If the orb list is opened, animate it to close (by springing state.position to 0), otherwise
// open it (spring state.position to 1).
let newPosition = (this._isOpened) ? 0 : 1;
// using a local prop vs state so that we don't have to trigger any re-render. At all costs, you generally
// want to avoid re-renders that are triggered at the beginning of an animation. Re-renders after an animation is over is okay,
// but re-renders when animations start can cause "jank" very easily
// Since all of our Animated values are connected by a spring, we wonly have to change the value of the first spring:
Animated.spring(this.state.position, { toValue: newPosition, ...springConfig }).start();
// and now all of our other values will follow this one...using physics! Neat!
this._isOpened = !!newPosition;
}
}
const styles = StyleSheet.create({
container: {
position: 'absolute',
top: 0,
left: 0,
right: 0,
bottom: 0,
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#DDDDDD',
},
openerButton: {
width: 70,
height: 70,
alignItems: 'center',
justifyContent: 'center',
position: 'relative',
},
openerButtonTouchable: {
borderRadius: 70,
backgroundColor: 'black',
position: 'absolute',
top: 0,
left: 0,
right: 0,
bottom: 0,
},
openerOrbButton: {
width: 40,
height: 40,
borderRadius: 40,
backgroundColor: 'white',
position: 'absolute',
top: 15,
left: 15,
},
});
export default AnimatedDemo;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment