From d5294dc58030e9ddb0683dfa2ab8675b903e0893 Mon Sep 17 00:00:00 2001 From: barsa Date: Wed, 4 Mar 2026 18:39:15 +0900 Subject: [PATCH] fix: improve infinite carousel navigation and transition handling - Updated the track index logic to prevent navigation while at clone positions, ensuring smoother transitions. - Enhanced the handleTransitionEnd function to only respond to the track's own transform transitions, improving event handling accuracy. - Adjusted dependencies in useCallback hooks for better performance and reliability. --- .../landing-page/hooks/useInfiniteCarousel.ts | 40 +++++++++++++------ 1 file changed, 27 insertions(+), 13 deletions(-) diff --git a/apps/portal/src/features/landing-page/hooks/useInfiniteCarousel.ts b/apps/portal/src/features/landing-page/hooks/useInfiniteCarousel.ts index 5b45a3b3..226b6186 100644 --- a/apps/portal/src/features/landing-page/hooks/useInfiniteCarousel.ts +++ b/apps/portal/src/features/landing-page/hooks/useInfiniteCarousel.ts @@ -48,10 +48,13 @@ export function useInfiniteCarousel({ items }: { items: T[] }) { const startAuto = useCallback(() => { if (autoRef.current) clearInterval(autoRef.current); autoRef.current = setInterval(() => { - setTrackIndex(prev => prev + 1); + setTrackIndex(prev => { + if (prev <= 0 || prev >= total + 1) return prev; + return prev + 1; + }); setIsTransitioning(true); }, AUTO_INTERVAL); - }, []); + }, [total]); const stopAuto = useCallback(() => { if (autoRef.current) { @@ -65,15 +68,22 @@ export function useInfiniteCarousel({ items }: { items: T[] }) { return stopAuto; }, [startAuto, stopAuto]); - const handleTransitionEnd = useCallback(() => { - if (trackIndex >= total + 1) { - setIsTransitioning(false); - setTrackIndex(1); - } else if (trackIndex <= 0) { - setIsTransitioning(false); - setTrackIndex(total); - } - }, [trackIndex, total]); + const handleTransitionEnd = useCallback( + (e: React.TransitionEvent) => { + // Only respond to the track's own transform transition, + // not bubbled events from child slide transitions (scale/opacity/filter) + if (e.target !== e.currentTarget || e.propertyName !== "transform") return; + + if (trackIndex >= total + 1) { + setIsTransitioning(false); + setTrackIndex(1); + } else if (trackIndex <= 0) { + setIsTransitioning(false); + setTrackIndex(total); + } + }, + [trackIndex, total] + ); useEffect(() => { if (isTransitioning) return; @@ -83,11 +93,15 @@ export function useInfiniteCarousel({ items }: { items: T[] }) { const navigate = useCallback( (updater: number | ((prev: number) => number)) => { - setTrackIndex(updater); + setTrackIndex(prev => { + // Block navigation while at a clone position (snap-back pending) + if (prev <= 0 || prev >= total + 1) return prev; + return typeof updater === "function" ? updater(prev) : updater; + }); setIsTransitioning(true); startAuto(); }, - [startAuto] + [startAuto, total] ); const goTo = useCallback((i: number) => navigate(i + 1), [navigate]);