ifelsething logoifelsething

Scroll Events of React Native ScrollView

Posted By: Harish
Scroll Events of React Native ScrollView

On scroll, different events get triggered. In general, we have onScrollBeginDrag, onScrollEndDrag, onScroll, onMomentumScrollBegin and onMomentumScrollEnd callbacks are available for ScrollView scroll.

Each callback is called when a specific action occurs. Lets check the actions which trigger these callbacks below.

Create A New Project

Create a new react-native project by using npx. Check documentation for creating a new react native project.

npx react-native@latest init ScrollViewRN

Example Implementation

We will create a scrollview with color blocks and perform a scroll to know its events.

Import and add ScrollView with some color blocks.

//App.tsx
...
import { View, ScrollView } from 'react-native';
...
<ScrollView
  contentContainerStyle={styles.content_container}
>
  {
    colors
      .map((color: string) => {
        return (
          <View
            key={color}
            style={[
              styles.view,
              {
                backgroundColor: color
              }
            ]}
          >
          </View>
        )
      })
  }
</ScrollView>
...

If we run the app,

#for Android
npx react-native run-android

#for ios
npx react-native run-ios

We will see some scrollable color blocks.

First add onScroll, onScrollBeginDrag and onScrollEndDrag callbacks to scrollview and console log the events.

...
<ScrollView
  ...
  onScroll={e => console.log("scroll ", e.nativeEvent)}
  onScrollBeginDrag={e => console.log("scroll begin ", e.nativeEvent)}
  onScrollEndDrag={e => console.log("scroll end ", e.nativeEvent)}
>
...
</ScrollView>
...

If we re-run the metro builder and start to scroll, you can observe that onScrollBeginDrag event calls first as we touch the scrollview, followed by onScroll event when we drag and finally onScrollEndDrag callback is called when we release the press after scrolling.

onScroll event will return events as long as the list scrolls and it will give performance issues. To avoid that, we have to use another prop scrollEventThrottle which accepts a number to limit the number of scroll events to return for a scroll. If the value is 0, it will limit to one output for start and end of a scroll.

Now add onMomentumScrollBegin and onMomentumScrollEnd callbacks and console log the events.

...
<ScrollView
  ...
  onMomentumScrollBegin={e => console.log("momentum begin ", e.nativeEvent)}
  onMomentumScrollEnd={e => console.log("momentum end", e.nativeEvent)}
>
...
</ScrollView>
...

Again perform scroll and notice when these two callbacks are called. These are called when the user lifts the press after the scroll, when momentum starts and ends.

 LOG  scroll begin  {"contentInset": {"bottom": 0, "left": 0, "right": 0, "top": 0}, "contentOffset": {"x": 0, "y": 0}, "contentSize": {"height": 2090, "width": 355}, "layoutMeasurement": {"height": 551, "width": 355}, "zoomScale": 1}
 LOG  scroll  {"contentInset": {"bottom": 0, "left": 0, "right": 0, "top": 0}, "contentOffset": {"x": 0, "y": 22}, "contentSize": {"height": 2090, "width": 355}, "layoutMeasurement": {"height": 551, "width": 355}, "zoomScale": 1}
 LOG  scroll end  {"contentInset": {"bottom": 0, "left": 0, "right": 0, "top": 0}, "contentOffset": {"x": 0, "y": 22}, "contentSize": {"height": 2090, "width": 355}, "layoutMeasurement": {"height": 551, "width": 355}, "targetContentOffset": {"x": 0, "y": 279}, "velocity": {"x": 0, "y": 0.5246273682119559}, "zoomScale": 1}
 LOG  momentum begin  {"contentInset": {"bottom": 0, "left": 0, "right": 0, "top": 0}, "contentOffset": {"x": 0, "y": 22}, "contentSize": {"height": 2090, "width": 355}, "layoutMeasurement": {"height": 551, "width": 355}, "zoomScale": 1}
 LOG  scroll  {"contentInset": {"bottom": 0, "left": 0, "right": 0, "top": 0}, "contentOffset": {"x": 0, "y": 279}, "contentSize": {"height": 2090, "width": 355}, "layoutMeasurement": {"height": 551, "width": 355}, "zoomScale": 1}
 LOG  momentum end {"contentInset": {"bottom": 0, "left": 0, "right": 0, "top": 0}, "contentOffset": {"x": 0, "y": 279}, "contentSize": {"height": 2090, "width": 355}, "layoutMeasurement": {"height": 551, "width": 355}, "zoomScale": 1}

By checking the above logs, we can see the order of these occurred events. Even if we decelerate the speed of the scroll the order will be the same.

Complete code of our example,

//App.tsx
import React from "react";
import {
  Text,
  StyleSheet,
  SafeAreaView,
  StatusBar,
  View,
  ScrollView,
} from "react-native";

const colors = [
  'orange',
  'green',
  'blue',
  'maroon',
  'violet',
  'darkorange',
  'gold',
  'darkgreen',
  'aquamarine',
  'cadetblue'
];

export default function App() {
  return (
    <SafeAreaView style={{ flex: 1, backgroundColor: 'white' }}>
      <StatusBar
        barStyle="dark-content"
      />
      <View style={styles.container}>
        <Text style={styles.text}>
          ifelsething.com
        </Text>
        <Text style={styles.text}>
          scroll events
        </Text>
        <ScrollView
          scrollEventThrottle={0}
          onScroll={e => console.log("scroll ", e.nativeEvent)}
          onScrollBeginDrag={e => console.log("scroll begin ", e.nativeEvent)}
          onScrollEndDrag={e => console.log("scroll end ", e.nativeEvent)}
          onMomentumScrollBegin={e => console.log("momentum begin ", e.nativeEvent)}
          onMomentumScrollEnd={e => console.log("momentum end", e.nativeEvent)}
          contentContainerStyle={styles.content_container}
        >
          {
            colors
              .map((color: string) => {
                return (
                  <View
                    key={string}
                    style={[
                      styles.view,
                      {
                        backgroundColor: color
                      }
                    ]}
                  >
                  </View>
                )
              })
          }
        </ScrollView>
      </View>
    </SafeAreaView>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    margin: 10,
    gap: 20
  },
  text: {
    fontSize: 15,
    color: 'black',
    fontStyle: 'italic'
  },
  content_container: {
    gap: 10
  },
  view: {
    width: '100%',
    height: 200,
    borderRadius: 10
  }
});

Share is Caring

Related Posts