-
Notifications
You must be signed in to change notification settings - Fork 1
Description
Issue: Image overlay tap-to-close animation fails after a prior drag-to-close
Summary
When dismissing the fullscreen image overlay by tapping outside the image, the close animation (spring + blur) sometimes does not run—the overlay disappears instantly. This happens reliably after the user has previously closed the overlay by dragging (pan-to-dismiss). If the user has only ever used tap-outside to close, the animation works every time.
Reproduction
- Open an image in the fullscreen overlay (tap a thumbnail in the feed).
- Drag to dismiss (pan down/away past the threshold so it closes with the spring animation).
- Open another (or the same) image again in the overlay.
- Dismiss by tapping outside the image (on the dark backdrop).
- Actual: The overlay closes but without the spring/blur close animation (instant disappear).
- Expected: The same spring + blur close animation as when using tap-outside the first time, or when using the close button.
What works
- Tap outside → close (first time, or when the previous close was also tap-outside): animation runs.
- Drag to close: animation runs.
- Close button: animation runs.
- Tap outside → close when the previous close was drag: overlay closes but no animation.
Technical context
Architecture
-
Provider:
components/blocks/nostr/image-overlay-provider.tsx- Holds shared values:
imageXCoord,imageYCoord,imageWidth,imageHeight,blurIntensity,closeBtnOpacity,imageState,isClosing, etc. close()is a worklet that setsisClosing = true, runswithSpringon position/size andwithTimingon blur/opacity to the thumbnail target, and on the 4th spring completion callsscheduleOnRN(finishClose).finishClosesetsimageState = 'close',isClosing = false, and clearsactiveUrlafter a short delay.open()is a JS callback: sets React state, shared values (thumbnail position + targets), then either startedwithTimingfrom JS to expand to center or (after attempted fix) callsscheduleOnUI(openToCenter)so the expand runs on the UI thread.openToCenter()is a worklet that runswithTimingto center/fullscreen.
- Holds shared values:
-
Overlay:
components/blocks/nostr/animated-image-overlay.tsx- Uses
Gesture.Exclusive(pan, tapBackdrop). Pan hasminDistance(10)so taps don’t trigger pan. - Tap outside: Tap
onEnd→ hit-test outside image →runOnJS(triggerClose)()→triggerClose()callsscheduleOnUI(closeRef.current)soclose()runs on the UI thread. - Drag to close: Pan
onFinalize→ ifdismissed, callsclose()directly (already on UI thread). - Close button:
onPress={triggerClose}→scheduleOnUI(closeRef.current).
- Uses
Hypothesis (why drag then tap breaks animation)
- Pan-close runs entirely on the UI thread (gesture →
close()worklet →withSpring/withTiming). When the springs finish, the last writes toimageXCoord, etc. happen in spring callbacks on the UI thread. - Open runs on the JS thread. It sets shared values and (before fix) started
withTimingfrom JS to expand to center. There may be a Reanimated/threading behavior where starting a new animation from the JS thread on shared values that were just animated on the UI thread does not run correctly (e.g. the expandwithTimingnever runs or is dropped). - So after “drag to close → open again”, the expand animation might never run; values stay at thumbnail. When the user tap-to-closes,
close()runs and doeswithSpring(x, …)etc. If current value is already at thumbnail, current === target, so springs complete immediately → no visible animation; overlay still closes becausefinishCloseruns.
What was tried (did not fix)
- Stale close reference: Using a ref for
closeandrunOnJS(triggerClose)()→scheduleOnUI(closeRef.current)so tap always invokes the currentclose. Tap-outside still fails after a prior drag-close. - isClosing / double-close guard: Early-return in
close()ifimageState !== 'open'orisClosing.value. No change. - Pan vs tap:
Pan().minDistance(10)so tap wins when there’s no movement. No change. - Run expand on UI thread: In
open(), stop startingwithTimingfrom JS; instead callcancelAnimationon the position/size/blur shared values, thenscheduleOnUI(openToCenter)so the expand-to-center animation runs on the UI thread. Issue persisted for the reporter. - cancelAnimation in open(): Cancel any in-flight animation on
imageXCoord,imageYCoord,imageWidth,imageHeight,blurIntensity,closeBtnOpacitybefore schedulingopenToCenter. Issue persisted.
So the root cause is likely deeper (e.g. Reanimated internal state after UI-thread animations, or gesture handler state after Gesture.Exclusive with pan + tap).
Environment
- React Native + Expo (project: Sovran).
react-native-reanimated,react-native-gesture-handler,react-native-worklets(scheduleOnUI / scheduleOnRN).- Fullscreen overlay uses
FullWindowOverlayfromreact-native-screenson native.
Possible next steps
- Reproduce with Reanimated/gesture-handler at specific versions and check release notes / issues for “animation from JS after UI animation” or “shared value not animating”.
- Add temporary logging in the overlay and provider (e.g. in
close()worklet and inopen()/openToCenter) to confirm: (1) tap path runsclose()and (2) on second open, expand animation (oropenToCenter) actually runs and updates the shared values. - Try a single “request close” entry point that always runs the same close worklet on the UI thread (tap, close button, and pan all go through it) and ensure no other code path sets
imageStateor clearsactiveUrlbefore the animation completes. - Try reversing gesture order to
Gesture.Exclusive(tapBackdrop, pan)to see if tap consistently wins after a prior pan.
Use this text to open a new GitHub issue: copy the contents of this file into the issue body and set the title to:
Image overlay: tap-to-close animation doesn’t run after a prior drag-to-close.