From 319ba37a11c636e537fe59c0885155b67e50b1f1 Mon Sep 17 00:00:00 2001 From: vineyardbovines Date: Wed, 1 Apr 2026 16:16:13 -0400 Subject: [PATCH] web deceleration --- src/components/images/Gallery/index.web.tsx | 48 +++++++++++++++++---- 1 file changed, 40 insertions(+), 8 deletions(-) diff --git a/src/components/images/Gallery/index.web.tsx b/src/components/images/Gallery/index.web.tsx index d490689a9f..ad4adad936 100644 --- a/src/components/images/Gallery/index.web.tsx +++ b/src/components/images/Gallery/index.web.tsx @@ -93,15 +93,19 @@ export function Gallery({ // Attach click-and-drag listeners directly to the scroll DOM node el.style.cursor = 'grab' + let lastMoveX = 0 + let lastMoveTime = 0 el.addEventListener('mousedown', (e: MouseEvent) => { if (e.button !== 0) return isDragging.current = true - hasDragged.current = false + hasDragged.current = true // assume drag; clear on mouseup if no movement dragStartX.current = e.clientX dragScrollStart.current = el.scrollLeft - el.style.scrollBehavior = 'auto' + lastMoveX = e.clientX + lastMoveTime = Date.now() el.style.scrollSnapType = 'none' + el.style.scrollBehavior = 'auto' el.style.cursor = 'grabbing' el.style.userSelect = 'none' e.preventDefault() @@ -109,22 +113,50 @@ export function Gallery({ el.addEventListener('mousemove', (e: MouseEvent) => { if (!isDragging.current) return - const dx = e.clientX - dragStartX.current - if (Math.abs(dx) > 3) hasDragged.current = true - el.scrollLeft = dragScrollStart.current - dx + lastMoveX = e.clientX + lastMoveTime = Date.now() + el.scrollLeft = dragScrollStart.current - (e.clientX - dragStartX.current) }) - const onUp = () => { + const onUp = (e: MouseEvent) => { if (!isDragging.current) return isDragging.current = false + // Only allow click-through if mouse barely moved (true click) + if (Math.abs(e.clientX - dragStartX.current) <= 3) { + hasDragged.current = false + } + + // Apply momentum: coast based on release velocity, then re-enable snap + const dt = Date.now() - lastMoveTime + const velocity = + dt < 100 ? (lastMoveX - dragStartX.current) / Math.max(dt, 1) : 0 + const momentum = velocity * -800 // scale velocity to scroll distance + el.style.scrollBehavior = 'smooth' - el.style.scrollSnapType = 'x proximity' + el.scrollLeft += momentum + // Re-enable snap after momentum scroll settles + requestAnimationFrame(() => { + el.style.scrollSnapType = 'x proximity' + }) + el.style.cursor = 'grab' el.style.userSelect = '' } el.addEventListener('mouseup', onUp) - el.addEventListener('mouseleave', onUp) + el.addEventListener('mouseleave', onUp as EventListener) + + // Intercept click events during/after drag to prevent Pressable onPress + el.addEventListener( + 'click', + (e: MouseEvent) => { + if (hasDragged.current) { + e.stopPropagation() + e.preventDefault() + } + }, + true, // capture phase — runs before React's synthetic events + ) } const handleKeyDown = (e: React.KeyboardEvent) => {