Skip to content

FIX overscrollEnabled behavior to properly clamp carousel edges #915

@hazimalperata

Description

@hazimalperata

Hi! 👋

Firstly, thanks for your work on this project! 🙂

I noticed that even when overscrollEnabled is set to false, the carousel could still overscroll beyond its bounds. For example:

  • When swiping left on the first slide, the carousel would move slightly past the first item.
  • When swiping right on the last slide, it could overshoot beyond the last item.

The problem was that the translation value was still being updated with the pan gesture even when overscrollEnabled was false. This diff it by clamping the translation strictly between the first and last items whenever overscrollEnabled is false. In other words, it prevents any overscroll by limiting the translation.value to the valid range.

Problem:

Even when overscrollEnabled = false, the carousel could still be overscrolled at the first or last slide.

Previous logic:

translation.value = panOffset.value + panTranslation;

Here:

  • panTranslation is the user’s drag movement
  • panOffset.value is the current carousel position

Example:

If the user is at the last slide (translation.value = -max.value)
And swipes from right to left (panTranslation < 0)

nextTranslation = panOffset.value + panTranslation
                = -max + (-10)
                = -max - 10

Without bounds checking, translation.value goes beyond the last slide → overscroll happens, even if overscrollEnabled is false.

Solution:

Clamp the translation mathematically:

translation.value = Math.max(-max.value, Math.min(0, nextTranslation));

  • lowerBound = -max.value → last slide
  • upperBound = 0 → first slide
  • nextTranslation = panOffset.value + panTranslation

Now:

If nextTranslation < lowerBound → translation.value = lowerBound
If nextTranslation > upperBound → translation.value = upperBound
Values in between → normal drag works

Examples:

First slide, swiping left:

panOffset.value = 0
panTranslation = 5
nextTranslation = 5
translation.value = Math.min(0, 5) = 0

Result: Overscroll prevented

Last slide, swiping right:

panOffset.value = -max.value = -300
panTranslation = -20
nextTranslation = -320
translation.value = Math.max(-300, -320) = -300

Result: Overscroll prevented

Middle slide, normal drag:

panOffset.value = -150
panTranslation = 30
nextTranslation = -120
translation.value = Math.max(-300, Math.min(0, -120)) = -120

Result: Normal drag works

Here is the diff that solved my problem:

diff --git a/node_modules/react-native-reanimated-carousel/src/components/ScrollViewGesture.tsx b/node_modules/react-native-reanimated-carousel/src/components/ScrollViewGesture.tsx
index c935c4b..adac52b 100644
--- a/node_modules/react-native-reanimated-carousel/src/components/ScrollViewGesture.tsx
+++ b/node_modules/react-native-reanimated-carousel/src/components/ScrollViewGesture.tsx
@@ -326,18 +326,28 @@ const IScrollViewGesture: React.FC<PropsWithChildren<Props>> = (props) => {
       if (fixedDirection === "negative") panTranslation = -Math.abs(panTranslation);
       else if (fixedDirection === "positive") panTranslation = +Math.abs(panTranslation);
 
+      const lowerBound = -max.value;
+      const upperBound = 0;
+      const nextTranslation = panOffset.value + panTranslation;
+
       if (!loop) {
-        if (translation.value > 0 || translation.value < -max.value) {
-          const boundary = translation.value > 0 ? 0 : -max.value;
-          const fixed = boundary - panOffset.value;
-          const dynamic = panTranslation - fixed;
-          translation.value = boundary + dynamic * 0.5;
+        if (!overscrollEnabled) {
+          translation.value = Math.max(lowerBound, Math.min(upperBound, nextTranslation));
+          return;
+        }
+
+        if (nextTranslation > upperBound) {
+          translation.value = upperBound + (nextTranslation - upperBound) * 0.5;
+          return;
+        }
+
+        if (nextTranslation < lowerBound) {
+          translation.value = lowerBound + (nextTranslation - lowerBound) * 0.5;
           return;
         }
       }
 
-      const translationValue = panOffset.value + panTranslation;
-      translation.value = translationValue;
+      translation.value = nextTranslation;
     },
     [
       isHorizontal,

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions