DEV Community

Alexander Senichev
Alexander Senichev

Posted on • Edited on

Image scaling by the vertical scrolling bounce for iOS in React Native in 5 minutes.

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,
  }
});
Enter fullscreen mode Exit fullscreen mode

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>
  );
}
Enter fullscreen mode Exit fullscreen mode

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

Top comments (0)