Skip to content

Commit 44f6315

Browse files
authored
Fix Swipeable misalignment after resizing on web (#3341)
## Description This PR allows Swipeable to be dynamically resized. Fixes #3333 ## Test plan - open any swipeable example on web - resize window - see how the `ReanimatedSwipeable` still works, while the legacy `Swipeable` is misaligned
1 parent cb2c528 commit 44f6315

File tree

1 file changed

+83
-64
lines changed

1 file changed

+83
-64
lines changed

src/components/ReanimatedSwipeable.tsx

Lines changed: 83 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -18,15 +18,18 @@ import {
1818
import type { PanGestureHandlerProps } from '../handlers/PanGestureHandler';
1919
import type { PanGestureHandlerEventPayload } from '../handlers/GestureHandlerEventPayload';
2020
import Animated, {
21+
ReduceMotion,
2122
SharedValue,
2223
interpolate,
24+
measure,
2325
runOnJS,
26+
runOnUI,
27+
useAnimatedRef,
2428
useAnimatedStyle,
2529
useSharedValue,
2630
withSpring,
2731
} from 'react-native-reanimated';
2832
import {
29-
Dimensions,
3033
I18nManager,
3134
LayoutChangeEvent,
3235
StyleProp,
@@ -257,20 +260,9 @@ const Swipeable = forwardRef<SwipeableMethods, SwipeableProps>(
257260
const leftWidth = useSharedValue<number>(0);
258261
const rightWidth = useSharedValue<number>(0);
259262

260-
// used for synchronizing layout measurements between JS and UI
261-
const rightOffset = useSharedValue<number | null>(null);
262-
263263
const showLeftProgress = useSharedValue<number>(0);
264264
const showRightProgress = useSharedValue<number>(0);
265265

266-
const updateRightElementWidth = useCallback(() => {
267-
'worklet';
268-
if (rightOffset.value === null) {
269-
rightOffset.value = rowWidth.value;
270-
}
271-
rightWidth.value = Math.max(0, rowWidth.value - rightOffset.value);
272-
}, [rightOffset, rightWidth, rowWidth]);
273-
274266
const updateAnimatedEvent = useCallback(() => {
275267
'worklet';
276268

@@ -372,11 +364,12 @@ const Swipeable = forwardRef<SwipeableMethods, SwipeableProps>(
372364
'worklet';
373365

374366
const translationSpringConfig = {
375-
duration: 1000,
376-
dampingRatio: 0.9,
377-
stiffness: 500,
367+
mass: 2,
368+
damping: 1000,
369+
stiffness: 700,
378370
velocity: velocityX,
379371
overshootClamping: true,
372+
reduceMotion: ReduceMotion.System,
380373
...animationOptions,
381374
};
382375

@@ -412,16 +405,17 @@ const Swipeable = forwardRef<SwipeableMethods, SwipeableProps>(
412405
}
413406
);
414407

415-
const progressTarget = toValue === 0 ? 0 : 1;
408+
const progressTarget = toValue === 0 ? 0 : 1 * Math.sign(toValue);
416409

417-
showLeftProgress.value =
418-
showLeftProgress.value > 0
419-
? withSpring(progressTarget, progressSpringConfig)
420-
: 0;
421-
showRightProgress.value =
422-
showRightProgress.value > 0
423-
? withSpring(progressTarget, progressSpringConfig)
424-
: 0;
410+
showLeftProgress.value = withSpring(
411+
Math.max(progressTarget, 0),
412+
progressSpringConfig
413+
);
414+
415+
showRightProgress.value = withSpring(
416+
Math.max(-progressTarget, 0),
417+
progressSpringConfig
418+
);
425419

426420
dispatchImmediateEvents(frozenRowState, toValue);
427421

@@ -440,20 +434,66 @@ const Swipeable = forwardRef<SwipeableMethods, SwipeableProps>(
440434
]
441435
);
442436

437+
const leftLayoutRef = useAnimatedRef();
438+
const leftWrapperLayoutRef = useAnimatedRef();
439+
const rightLayoutRef = useAnimatedRef();
440+
441+
const updateElementWidths = useCallback(() => {
442+
'worklet';
443+
const leftLayout = measure(leftLayoutRef);
444+
const leftWrapperLayout = measure(leftWrapperLayoutRef);
445+
const rightLayout = measure(rightLayoutRef);
446+
leftWidth.value =
447+
(leftLayout?.pageX ?? 0) - (leftWrapperLayout?.pageX ?? 0);
448+
449+
rightWidth.value =
450+
rowWidth.value -
451+
(rightLayout?.pageX ?? rowWidth.value) +
452+
(leftWrapperLayout?.pageX ?? 0);
453+
}, [
454+
leftLayoutRef,
455+
leftWrapperLayoutRef,
456+
rightLayoutRef,
457+
leftWidth,
458+
rightWidth,
459+
rowWidth.value,
460+
]);
461+
443462
const swipeableMethods = useMemo<SwipeableMethods>(
444463
() => ({
445464
close() {
446465
'worklet';
447-
animateRow(0);
466+
if (_WORKLET) {
467+
animateRow(0);
468+
return;
469+
}
470+
runOnUI(() => {
471+
animateRow(0);
472+
})();
448473
},
449474
openLeft() {
450475
'worklet';
451-
animateRow(leftWidth.value);
476+
if (_WORKLET) {
477+
updateElementWidths();
478+
animateRow(leftWidth.value);
479+
return;
480+
}
481+
runOnUI(() => {
482+
updateElementWidths();
483+
animateRow(leftWidth.value);
484+
})();
452485
},
453486
openRight() {
454487
'worklet';
455-
// rightOffset and rowWidth are already much sooner than rightWidth
456-
animateRow((rightOffset.value ?? 0) - rowWidth.value);
488+
if (_WORKLET) {
489+
updateElementWidths();
490+
animateRow(-rightWidth.value);
491+
return;
492+
}
493+
runOnUI(() => {
494+
updateElementWidths();
495+
animateRow(-rightWidth.value);
496+
})();
457497
},
458498
reset() {
459499
'worklet';
@@ -464,14 +504,14 @@ const Swipeable = forwardRef<SwipeableMethods, SwipeableProps>(
464504
},
465505
}),
466506
[
507+
animateRow,
508+
updateElementWidths,
467509
leftWidth,
468-
rightOffset,
469-
rowWidth,
510+
rightWidth,
470511
userDrag,
471512
showLeftProgress,
472513
appliedTranslation,
473514
rowState,
474-
animateRow,
475515
]
476516
);
477517

@@ -484,38 +524,31 @@ const Swipeable = forwardRef<SwipeableMethods, SwipeableProps>(
484524

485525
// As stated in `Dimensions.get` docstring, this function should be called on every render
486526
// since dimensions may change (e.g. orientation change)
487-
const hiddenSwipeableOffset = Dimensions.get('window').width + 1;
488527

489528
const leftActionAnimation = useAnimatedStyle(() => {
490529
return {
491-
transform: [
492-
{
493-
translateX:
494-
showLeftProgress.value === 0 ? -hiddenSwipeableOffset : 0,
495-
},
496-
],
530+
opacity: showLeftProgress.value === 0 ? 0 : 1,
497531
};
498532
});
499533

500534
const leftElement = useCallback(
501535
() => (
502-
<Animated.View style={[styles.leftActions, leftActionAnimation]}>
536+
<Animated.View
537+
ref={leftWrapperLayoutRef}
538+
style={[styles.leftActions, leftActionAnimation]}>
503539
{renderLeftActions?.(
504540
showLeftProgress,
505541
appliedTranslation,
506542
swipeableMethods
507543
)}
508-
<View
509-
onLayout={({ nativeEvent }) =>
510-
(leftWidth.value = nativeEvent.layout.x)
511-
}
512-
/>
544+
<Animated.View ref={leftLayoutRef} />
513545
</Animated.View>
514546
),
515547
[
516548
appliedTranslation,
517549
leftActionAnimation,
518-
leftWidth,
550+
leftLayoutRef,
551+
leftWrapperLayoutRef,
519552
renderLeftActions,
520553
showLeftProgress,
521554
swipeableMethods,
@@ -524,12 +557,7 @@ const Swipeable = forwardRef<SwipeableMethods, SwipeableProps>(
524557

525558
const rightActionAnimation = useAnimatedStyle(() => {
526559
return {
527-
transform: [
528-
{
529-
translateX:
530-
showRightProgress.value === 0 ? hiddenSwipeableOffset : 0,
531-
},
532-
],
560+
opacity: showRightProgress.value === 0 ? 0 : 1,
533561
};
534562
});
535563

@@ -541,18 +569,14 @@ const Swipeable = forwardRef<SwipeableMethods, SwipeableProps>(
541569
appliedTranslation,
542570
swipeableMethods
543571
)}
544-
<View
545-
onLayout={({ nativeEvent }) => {
546-
rightOffset.value = nativeEvent.layout.x;
547-
}}
548-
/>
572+
<Animated.View ref={rightLayoutRef} />
549573
</Animated.View>
550574
),
551575
[
552576
appliedTranslation,
553577
renderRightActions,
554578
rightActionAnimation,
555-
rightOffset,
579+
rightLayoutRef,
556580
showRightProgress,
557581
swipeableMethods,
558582
]
@@ -564,8 +588,6 @@ const Swipeable = forwardRef<SwipeableMethods, SwipeableProps>(
564588
const { velocityX } = event;
565589
userDrag.value = event.translationX;
566590

567-
updateRightElementWidth();
568-
569591
const leftThresholdProp = leftThreshold ?? leftWidth.value / 2;
570592
const rightThresholdProp = rightThreshold ?? rightWidth.value / 2;
571593

@@ -603,7 +625,6 @@ const Swipeable = forwardRef<SwipeableMethods, SwipeableProps>(
603625
rightWidth,
604626
rowState,
605627
userDrag,
606-
updateRightElementWidth,
607628
]
608629
);
609630

@@ -632,9 +653,7 @@ const Swipeable = forwardRef<SwipeableMethods, SwipeableProps>(
632653
.enabled(enabled !== false)
633654
.enableTrackpadTwoFingerGesture(enableTrackpadTwoFingerGesture)
634655
.activeOffsetX([-dragOffsetFromRightEdge, dragOffsetFromLeftEdge])
635-
.onStart(() => {
636-
updateRightElementWidth();
637-
})
656+
.onStart(updateElementWidths)
638657
.onUpdate(
639658
(event: GestureUpdateEvent<PanGestureHandlerEventPayload>) => {
640659
userDrag.value = event.translationX;
@@ -679,7 +698,7 @@ const Swipeable = forwardRef<SwipeableMethods, SwipeableProps>(
679698
onSwipeableOpenStartDrag,
680699
rowState,
681700
updateAnimatedEvent,
682-
updateRightElementWidth,
701+
updateElementWidths,
683702
userDrag,
684703
]
685704
);

0 commit comments

Comments
 (0)