Building off of our interpolations on interpolations we can also flip numbers. inputRange
is only able to accept values that move in an increasing fashion. However you may be constructing reversed animations that would require you to do an inputRange of [1, 0]
.
To accommodate this we can interpolate to an outputRange
that flips in reverse for us so that our animation is moving forward from 0 => 1
the interpolate will flip it to be animating from 1 => 0
, which then we can interpolate on our second animation in the correct direction [0, 1]
but it will actually be animating in reverse.
const animatedInterpolate = this.state.animation.interpolate({ inputRange: [0, 1], outputRange: [1, 0], }); const reversedDirection = animatedInterpolate.interpolate({ inputRange: [0, 1], outputRange: [1, 0.5], });
This can be confusing to understand, but just knowing that it's a thing will come in handy when you realize you need it.
Just interpolating numbers to different numbers is going to be your primary use case of using interpolate. This may be mapping values up, down, or any direction depending on what your animation requires.
Scaling values up would look something like this. We have an inputRange that will take an animation that is animating from 0
to 1
.
this.state.animation.interpolate({ inputRange: [0, 1], outputRange: [0, 100], });
Now if we were to kick off an animation
Animated.timing(this.state.animation, { toValue: 1, duration: 1000, }).start();
The output over the course of 1000ms
would churn out values like so.
input: 0 => 0ms => output: 0 input: .1 => 100ms => output: 10 input: .2 => 200ms => output: 20 input: .3 => 300ms => output: 30 .... input: 1 => 1000ms => output: 100
The same goes for the opposite direction. We can scale numbers down from larger => smaller
this.state.animation.interpolate({ inputRange: [0, 100], outputRange: [0, 1], });
Kicking off an animation
Animated.timing(this.state.animation, { toValue: 1, duration: 1000, }).start();
Would produce values
input: 0 => 0ms => output: 0 input: 10 => 100ms => output: .1 input: 20 => 200ms => output: .2 input: 30 => 300ms => output: .3 .... input: 100 => 1000ms => output: 1
These are examples where the inputRange
is producing a linear outputRange
, but the inputRange
is the only thing that needs to be moving in an increasing fashion. The outputRange
could be anything. The only requirement is that the inputRange
and outputRange
have the same number of items in their respective arrays.
Lets look at an interpolation
this.state.animation.interpolate({ inputRange: [0, 30, 50, 80, 100], outputRange: [0, -30, -50, 0, 200], });
Lets take a look at what values would be produced with the animation
Animated.timing(this.state.animation, { toValue: 1, duration: 1000, }).start();
input: 0 => 0ms => 0 input 15 => 150ms => -15 input 30 => 300ms => -30 input 50 => 500ms => -50 input 65 => 650ms => -25 input 80 => 800ms => 0 input 90 => 900ms => 100 input 100 => 100ms => 200
As you can see our animated value only ever increased, but our outputRange
intermediate values were interpolated correctly based upon the steps in between each range and it's targeted output range.
Understanding all the intermediate values can be necessary however, for many animations it's not critical. Interpolate will figure out the steps correctly based upon your duration, or spring.
Now realize that interpolate
is returning a new Animated.Value
so what that means is you can interpolate on an interpolate.
Lets look at an example of this.
export default class animations extends Component { state = { animation: new Animated.Value(0), }; startAnimation = () => { Animated.timing(this.state.animation, { toValue: 1, duration: 1500, }).start(() => { Animated.timing(this.state.animation, { toValue: 2, duration: 300, }).start(); }); }; render() { const animatedInterpolate = this.state.animation.interpolate({ inputRange: [0, 1, 2], outputRange: [0, 300, 0], }); const interpolatedInterpolate = animatedInterpolate.interpolate({ inputRange: [0, 300], outputRange: [1, 0.5], }); const animatedStyles = { transform: [{ translateY: animatedInterpolate }], opacity: interpolatedInterpolate, }; return ( <View style={styles.container}> <TouchableWithoutFeedback onPress={this.startAnimation}> <Animated.View style={[styles.box, animatedStyles]} /> </TouchableWithoutFeedback> </View> ); } } const styles = StyleSheet.create({ container: { flex: 1, alignItems: "center", justifyContent: "center", }, box: { width: 150, height: 150, backgroundColor: "tomato", }, });
Lets focus on the important piece.
Our animation is only going to 2, however our interpolateInterpolate
is referencing the values that are in the outputRange
of our first animated interpolate. This can be a powerful tool when passing around animated interpolations.
const animatedInterpolate = this.state.animation.interpolate({ inputRange: [0, 1, 2], outputRange: [0, 300, 0], }); const interpolatedInterpolate = animatedInterpolate.interpolate({ inputRange: [0, 300], outputRange: [1, 0.5], });
If you do not want to expose the original animated value, or your interpolation only operates across a specific range. You can interpolate an interpolate before passing it to something that will interpolate it.
Quick example, if we had an animation that went from 0
to 300
, but something required a range from 0
to 1
. We can map our desired inputRange to an outputRange that will feed into our second animation inputRange and derive our desired outputRange.
const animatedInterpolate = this.state.animation.interpolate({ inputRange: [0, 300], outputRange: [0, 1], extrapolate: "clamp", }); const interpolatedInterpolate = animatedInterpolate.interpolate({ inputRange: [0, 1], outputRange: [1, 0.5], });
Interpolating interpolations can be challenging to wrap your head around but can be extremely powerful in practice.