Update: Changed the project source to use Expo and changed the structure a little bit to use transforms instead of height to be able to leverage the native driver. See this commit for the changes I made.
Here’s a walkthrough that shows how to build a header that is animated with the scroll position of a ScrollView. I had to build something similar for an app and found out that it was really easy to do in React Native using the Animated API.
Here’s the final result:
How does it work?
The idea is to render a header over the ScrollView using position: ‘absolute’ and adding a margin to the top of the ScrollView to offset for the header. Then we can simply animate the header height using the ScrollView scroll position.
Time for some code!
Let’s start by creating a ScrollView with some content and import everything we will need for the next steps.
Next we need to create the header View and add a margin to the ScrollView content so it’s content is not under the header. We will also add a title for the header. Let’s add the following View after the ScrollView. We will use an Animated.View since it is going to be animated eventually.
And the following styles:
We will also define some constants for the header sizes that will be used to interpolate the scroll position value.
Now we should have the desired initial layout and everything ready for the next step.
React Native has a very powerful declarative animation API that allows to animate a value but also bind it’s value to an event. In this case we will want to bind an animated value to the ScrollView Y scroll position. To do so, we use an Animated.event with the ScrollView onScroll prop.
First we create an Animated.Value which is a simple value that can be animated using the Animated API.
Then we bind the animated value to the ScrollView scroll position. To do that we use an Animated.event with a mapping to the event object property that we want to bind to the animated value. In this case it is <eventObject>.nativeEvent.contentOffset.y.
Then we use the interpolate method of the Animated.Value to map the scroll position to the desired header height. What we want is that when the scroll position is at 0, the header is at HEADER_MAX_HEIGHT and when the scroll position has moved to the difference between HEADER_MAX_HEIGHT and HEADER_MIN_HEIGHT, the header is at HEADER_MIN_HEIGHT.
Finally we just set the height to the animated value for the header view. We also set the scrollEventThrottle prop to 16 so events are sent often enough to have a smooth animation.
At this point the header will move with the scroll position and using the same technique we can animate pretty much anything we want in the header while the user is scrolling. Time to use your creativity :)
Now this header is a bit boring let’s add our favorite cat picture with a parallax effect to make it nicer. To do this let’s add two new interpolated animated values and an Image component.
We use the same initial interpolation values but here we want to output an opacity value and a translation of the image to create the parallax effect. We also animate the opacity a bit differently by adding a 3rd breakpoint in the middle. This way the opacity will only start to decay when the header has scrolled halfway.
Now all that is missing is scaling / translating the header title a bit but I’ll leave that as an exercise.
The Animated API makes it very easy to create very complex animations by interpolating a value in multiple different ways. It also allows to keep the state super simple. Everything is based on one value, all the animation complexity is handled in the render function with the interpolations.