Stop Scrolling at Irregular Intervals in React Native
Published On: 2024-07-12
Posted By: Harish

We know the way to stop scrolling at regular intervals using snapToInterval prop. But with this, we can only stop scrolling at multiples of a given interval and not irregular positions.
So to overcome that, we have snapToOffsets prop, which accepts an array of numbers. Here, numbers are nothing but the y-axis position (content height) for vertical scrollview and x-axis position (content width) for horizontal scrollview. For every scroll, it stops at any given nearest number.
Let'ss see this in action.
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
For this example, we will create a simple horizontal scrollview with color blocks. Each color block will have a width of 200
and a gap of 10
. So, we will snap the scroll at different widths.
Import and add ScrollView
with few scrollable color blocks.
//App.tsx
...
import { View, ScrollView } from 'react-native';
...
<ScrollView
contentContainerStyle={styles.content_container}
horizontal
>
{
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 can see scrollable horizontal color blocks. If you scroll now, they will scroll freely till the momentum lasts.
Now add snapToOffsets
prop with an array of values 630
and 1050
.
Here, 630 is nothing but the sum of three blocks and their gaps. Each block's width is 200 and has a gap of 10. Similarly, 1050 is the sum of five color block widths.
And decelerationRate prop is used to adjust different scrolling speeds.
...
<ScrollView
...
decelerationRate='fast'
snapToOffsets={[630, 1050]}
>
...
</ScrollView>
...
Now reload the metro builder and try to scroll as fast as you can. We can see that no matter what the scrolling speed is, the scroll stops at the third block and if you continue scrolling, it again stops at the fifth block and lastly stops at the end of the content or till scroll momentum lasts.

This is because the content snaps at given intervals.
Snapping Effect
If you look closely, it stops at given intervals and the left edge gets snapped to that interval.
You can see this snapping effect by simple left or right swipe. If you observe the above gif, the offsets get snapped to the edge.
Snapping to Start and End
When we use snapToOffset prop, by default snapping is automatically applied to start and end of scrollview content. Means, the content scrolls back to start or end position for small pushes or pulls.
You can see from the gif below.

So, If you don't want that effect, we can use snapToStart and snapToEnd props to disable this feature. These props accept boolean values and default value is true
for both of them.
The snapToInterval prop lacks this functionality. By default they will snap to start and end positions.
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}>
snap scroll to offsets
</Text>
<ScrollView
horizontal
decelerationRate={'fast'}
snapToOffsets={[630, 1050]}
//snapToStart={false}
//snapToEnd={false}
contentContainerStyle={styles.content_container}
>
{
colors
.map((color: string) => {
return (
<View
key={color}
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: 200,
height: 200,
borderRadius: 10
}
});