In this article, I'm going to demonstrate an easy approach how to build 60fps animation for a list header (e.g. image), when you are doing a bounce effect by scrolling up in the iOS application.
I use Expo workflow since it's easy to get started and react-native-reanimated to make beauty and smooth animations:
Install dependencies and start the project:
npm i expo-cli
expo init my-app
expo install react-native-reanimated
Then create basic markup:
export default function App() {
return (
<View style={styles.container}>
<Image
style={styles.image}
source={{ uri: 'http://picsum.photos/1000/1000' }}
/>
<ScrollView contentContainerStyle={{ flexGrow: 1 }}>
<View style={styles.items}>
{new Array(15).fill(0).map((_, index) => <View key={index} style={styles.item} />)}
</View>
</ScrollView>
</View>
);
}
const IMAGE_HEIGHT = 300
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#000',
},
items: {
paddingTop: IMAGE_WIDTH,
paddingHorizontal: 8,
},
image: {
...StyleSheet.absoluteFillObject,
top: 0,
height: IMAGE_WIDTH,
width: '100%'
},
title: {
color: '#FFF'
},
item: {
backgroundColor: '#1C1C1C',
height: 50,
width: '100%',
marginTop: 8,
}
});
Now let's do animation:
- As you can see we use
ScrollViewandImagefromreact-native-reanimatedto get animated nodes. - Then we set up the throttle value to
16for theScrollViewnode to throttle invocation of events. -
useValue- hook to createAnimated.Value. -
Animated.eventtakes an array of mappings and extracts values from each arg accordingly. In our case, we extractcontentOffset.yand assign it toscrollY, so by native side value ofscrollYis always the same asoffsetYof ourAnimated.ScrollView. - Interpolations of the
offsetYofAnimated.ScrollViewforAnimated.Image:-
translateYis needed to translate the image to the top when we are scrolling down -
scalejust nicely scales withinoutputRangethe image byinputRange.
-
- Set
paddingTopfor the content.
export default function App() {
const scrollY = useValue(0)
return (
<View style={styles.container}>
<Animated.Image
style={{
...styles.image,
transform: [
{
translateY: interpolate(scrollY, {
inputRange: [0, IMAGE_WIDTH],
outputRange: [0, -IMAGE_WIDTH],
extrapolate: Extrapolate.CLAMP,
}),
scale: interpolate(scrollY, {
inputRange: [-IMAGE_WIDTH * 2, 0],
outputRange: [5, 1],
extrapolate: Extrapolate.CLAMP,
}),
},
],
}}
source={{ uri: 'http://picsum.photos/1000/1000' }}
/>
<Animated.ScrollView
onScroll={Animated.event(
[{ nativeEvent: { contentOffset: { y: scrollY } } }],
{ useNativeDriver: true }
)}
contentContainerStyle={{ flexGrow: 1 }}
scrollEventThrottle={16}
>
<View style={styles.items}>
{new Array(15).fill(0).map((_, index) => <View key={index} style={styles.item} />)}
</View>
</Animated.ScrollView>
</View>
);
}
That's all, hope you are well. Happy coding :)
Top comments (0)