Skip to content
New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

✨ Add animation lifecycle callbacks #75

Merged
merged 2 commits into from
Jan 21, 2025
Merged
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 48 additions & 1 deletion src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,8 @@ interface Props {
debounceDelay?: number;
animateOnVisible?: boolean | AnimateOnVisibleOptions;
startFromLastDigit?: boolean;
onAnimationStart?: () => void;
onAnimationEnd?: () => void;
}

function SlotCounter(
Expand Down Expand Up @@ -82,6 +84,8 @@ function SlotCounter(
debounceDelay,
animateOnVisible,
startFromLastDigit = false,
onAnimationStart,
onAnimationEnd,
}: Props,
ref: React.Ref<SlotCounterRef>,
) {
Expand Down Expand Up @@ -134,6 +138,23 @@ function SlotCounter(
startAnimationOptionsRef.current?.dummyCharacterCount ?? dummyCharacterCount;
const effectiveDuration = startAnimationOptionsRef.current?.duration ?? duration;

/**
* Callback Events ref for preventing unnecessary re-renders by avoiding dependency array
*/
const eventCallbackRef = useRef({
onAnimationStart: onAnimationStart,
onAnimationEnd: onAnimationEnd,
});
eventCallbackRef.current = {
onAnimationStart: onAnimationStart,
onAnimationEnd: onAnimationEnd,
};

/**
* Animation start and end event
*/
const isAnimatingRef = useRef(false);

/**
* Detect max number width
*/
Expand Down Expand Up @@ -239,6 +260,15 @@ function SlotCounter(
return Math.min(MAX_INTERVAL, effectiveDuration / valueList.length);
}, [effectiveDuration, valueList.length, delay]);

/**
* Handle transition end
*/
const handleTransitionEnd = useCallback(() => {
eventCallbackRef.current.onAnimationEnd?.();
isAnimatingRef.current = false;
numbersRef.current?.removeEventListener('transitionend', handleTransitionEnd);
}, []);

/**
* Start animation
*/
Expand All @@ -247,6 +277,20 @@ function SlotCounter(
window.cancelAnimationFrame(animationTimerRef.current);
}

// If animation is already started, call onAnimationEnd immediately
if (isAnimatingRef.current) {
handleTransitionEnd();
}

isAnimatingRef.current = true;

// If animation is not started, add event listener
numbersRef.current?.addEventListener('transitionend', handleTransitionEnd);

// Call onAnimationStart callback
eventCallbackRef.current.onAnimationStart?.();

// Set active to false and increment animation count
setActive(false);
animationCountRef.current = animationExecuteCountRef.current;
animationCountRef.current += 1;
Expand All @@ -261,7 +305,7 @@ function SlotCounter(
setActive(true);
});
});
}, []);
}, [handleTransitionEnd]);

/**
* Get sequential dummy list
Expand Down Expand Up @@ -368,6 +412,9 @@ function SlotCounter(
useValueChangeEffect(renderValueList);
const diffValueListCount = renderValueList.length - getPrevDependencies().length;

/**
* Start animation all
*/
const startAnimationAll = useCallback(
(options?: StartAnimationOptions) => {
if (startValue != null && !startValueOnce) prevValueRef.current = undefined;
Expand Down