-
-
Notifications
You must be signed in to change notification settings - Fork 1k
Description
Description
I discovered two animation bugs in ReanimatedSwipeable that cause poor UX during swipe interactions.
Bug 1: Right/Left actions disappear during fast close animation
When closing a swipeable quickly, the action views (left/right) disappear before the animation completes, leaving only the background visible.
Root Cause: The opacity of action containers is set conditionally:
opacity: showRightProgress.value === 0 ? 0 : 1When the progress value hits 0 (even momentarily during animation), the actions become invisible while the row is still animating back.
Fix: Set opacity to always be 1:
opacity: 1The actions are already clipped by overflow: 'hidden' on the container, so they don't need opacity-based hiding.
Bug 2: Fast swipe causes instant snap without animation
When swiping quickly, the row snaps instantly without any visible animation. This happens in two scenarios:
- Closing from open: Swipe fast to close → snaps instantly
- Staying open: Swipe open, then swipe back fast (but not enough to close) → snaps back to open instantly
Slow swipes animate smoothly in both cases.
Root Cause: When you swipe fast, the velocity opposes the spring's target direction:
- Example (closing):
currentPos = -20,velocity = -300(moving left),target = 0(right) - Example (staying open):
currentPos = -80,velocity = +300(moving right),target = -100(left)
With overshootClamping: true and high stiffness, the spring fights against the velocity and completes in just a few frames, appearing instant.
Fix: Ignore velocity when it opposes the direction towards the target:
const currentPos = appliedTranslation.value;
const movingTowardsTarget = clampedVelocity
? (toValue > currentPos && clampedVelocity > 0) || (toValue < currentPos && clampedVelocity < 0)
: true;
const effectiveVelocity = !movingTowardsTarget ? 0 : clampedVelocity;This allows the spring to animate naturally without fighting the swipe momentum.
Steps to reproduce
Bug 1 (Actions disappearing):
- Create a
ReanimatedSwipeablewithrenderRightActions - Swipe left to reveal the right action
- Quickly swipe back to close
- Observe: The right action disappears mid-animation
Bug 2 (Instant snap - closing):
- Create a
ReanimatedSwipeablewithrenderRightActions - Swipe left very fast (quick flick) and release immediately
- Observe: The row snaps back instantly without animation
Bug 2 (Instant snap - staying open):
- Swipe left to fully open the swipeable (reveal right action)
- Swipe right very fast but release before it closes
- Observe: The row snaps back to open position instantly without animation
Compare all above with slow swipes - they animate smoothly.
Snack or a link to a repository
https://snack.expo.dev/@ugoi/reanimatedswipeable-animation-bugs
Gesture Handler version
2.28.0
React Native version
0.81.5
Platforms
| platform | affected |
|---|---|
| iOS | ✅ Yes |
| Android | ✅ Yes |
| Web | ✅ Yes |
Proposed Solution
I have working patches for both issues. Happy to submit a PR.
For Bug 1: Change lines 359 and 389 in ReanimatedSwipeable.tsx:
// Before
opacity: showRightProgress.value === 0 ? 0 : 1
// After
opacity: 1For Bug 2: Add velocity direction detection in animateRow function (~line 198):
const MAX_VELOCITY = 500;
const clampedVelocity = velocityX
? Math.max(-MAX_VELOCITY, Math.min(MAX_VELOCITY, velocityX))
: undefined;
const currentPos = appliedTranslation.value;
const movingTowardsTarget = clampedVelocity
? (toValue > currentPos && clampedVelocity > 0) || (toValue < currentPos && clampedVelocity < 0)
: true;
const effectiveVelocity = !movingTowardsTarget ? 0 : clampedVelocity;
const translationSpringConfig = {
// ... use effectiveVelocity instead of velocityX
};