In the world of touch screen and virtual buttons, providing visual feedback of an action is important to let your users know their touch interactions have been received by your app. If you find yourself rolling your own touchables in react native, you can use custom animations to respond to user interaction. Here’s a easy way to animate a the appearance of a “press”, which you can use in addition to other feedback, like opacity:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import * as React from 'react'; | |
import { Animated, Text, View, StyleSheet, TouchableOpacity } from 'react-native'; | |
import { Constants } from 'expo'; | |
// You can import from local files | |
import AssetExample from './components/AssetExample'; | |
// or any pure javascript modules available in npm | |
import { Card } from 'react-native-paper'; | |
export default class App extends React.Component { | |
scaleInAnimated = new Animated.Value(0); | |
scaleOutAnimated = new Animated.Value(0); | |
render() { | |
return ( | |
<View style={styles.container}> | |
<TouchableOpacity | |
onPress={() => { console.warn("I did it!")}} | |
onPressIn={() => {SCALE.pressInAnimation(this.scaleInAnimated);}} | |
onPressOut={() => {SCALE.pressOutAnimation(this.scaleInAnimated);}} | |
style={SCALE.getScaleTransformationStyle(this.scaleInAnimated)} | |
> | |
<Animated.View> | |
<Card> | |
<AssetExample /> | |
</Card> | |
</Animated.View> | |
</TouchableOpacity> | |
<View style={{margin: 10, height: 10}}/> | |
<TouchableOpacity | |
onPress={() => { console.warn("I did it!")}} | |
onPressIn={() => {SCALE.pressInAnimation(this.scaleOutAnimated);}} | |
onPressOut={() => {SCALE.pressOutAnimation(this.scaleOutAnimated);}} | |
style={SCALE.getScaleTransformationStyle(this.scaleOutAnimated, 1, 1.05)} | |
> | |
<Animated.View> | |
<Card> | |
<AssetExample /> | |
</Card> | |
</Animated.View> | |
</TouchableOpacity> | |
</View> | |
); | |
} | |
} | |
// For large projects, this might better serve us exported from it's own file, so we could use it across man classes. i.e ./animations/scale.js | |
const SCALE = { | |
// … see full contents below, or at https://gist.github.com/JoeM-RP/608c155f317a80634c2d1dfa4b962c2b | |
} | |
const styles = StyleSheet.create({ | |
container: { | |
flex: 1, | |
justifyContent: 'center', | |
paddingTop: Constants.statusBarHeight, | |
backgroundColor: '#fff', | |
padding: 18, | |
}, | |
}); |
Our animation is largely driven by these primary functions. Take a close look at getScaleTransformationStyle. We accept an Animated.Value, which is an object that describes the state of animation for a given element (or elements) on screen, as well as a start size (how large the element should be prior to animating) and an end size (how large the object should be at the end of the animation). “End” size is somewhat loose here, as we are relying on the touchable onPressOut (i.e – touch released) to return our element to it’s original state. Compare pressInAnimation to pressOutAnimation:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const SCALE = { | |
// this defines the terms of our scaling animation. | |
getScaleTransformationStyle(animated: Animated.Value, startSize: number = 1, endSize: number = 0.99) { | |
const interpolation = animated.interpolate({ | |
inputRange: [0, 1], | |
outputRange: [startSize, endSize], | |
}); | |
return { | |
transform: [ | |
{ scale: interpolation }, | |
], | |
}; | |
}, | |
// This defines animation behavior we expext onPressIn | |
pressInAnimation(animated: Animated.Value, duration: number = 150) { | |
animated.setValue(0); | |
Animated.timing(animated, { | |
toValue: 1, | |
duration, | |
useNativeDriver: true, | |
}).start(); | |
}, | |
// This defines animatiom behavior we expect onPressOut | |
pressOutAnimation(animated: Animated.Value, duration: number = 150) { | |
animated.setValue(1); | |
Animated.timing(animated, { | |
toValue: 0, | |
duration, | |
useNativeDriver: true, | |
}).start(); | |
}, | |
}; |
You’ll notice that the animated.setValue value corresponds to the expected inputRange value defined by our interpolation variable in getScaleTransformationStyle. Once we apply the style returned from getScaleTransformationStyle to a valid Animated.View, we can see our scaling adventures in action! Check out the expo sandbox or the quick demo below:

Not too bad, eh? Don’t forget: you can make SCALE its own file and export it for further reuse across multiple files. Enjoy!