Image scaling by the vertical scrolling bounce for iOS in React Native in 5 minutes.
Published: 2020-12-25 23:03:46

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:**
1. As you can see we use `ScrollView` and `Image` from `react-native-reanimated` to get animated nodes. 
2. Then we set up the throttle value to `16`  for the `ScrollView` node to throttle invocation of events. 
3. `useValue` - hook to create `Animated.Value`.
4. `Animated.event` takes an array of mappings and extracts values from each arg accordingly. In our case, we extract `contentOffset.y` and assign it to `scrollY`, so by native side value of `scrollY` is always the same as `offsetY` of our `Animated.ScrollView`.
5. Interpolations of the `offsetY` of `Animated.ScrollView` for `Animated.Image`:
   * `translateY` is needed to translate the image to the top when we are scrolling down
   * `scale` just nicely scales within `outputRange` the image by `inputRange`.
6. Set `paddingTop` for 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>
  );
}
```

{% youtube 8aoDLflQNlU %}

That's all, hope you are well. Happy coding :)