UNPKG

@gfazioli/mantine-text-animate

Version:

The TextAnimate component allows you to animate text with various effects.

159 lines (155 loc) 4.8 kB
'use client'; 'use strict'; var React = require('react'); const easingFunctions = { // No easing, no acceleration linear: (t) => t, // Accelerating from zero velocity "ease-in": (t) => t * t, // Decelerating to zero velocity "ease-out": (t) => 1 - Math.pow(1 - t, 2), // Acceleration until halfway, then deceleration "ease-in-out": (t) => t < 0.5 ? 2 * t * t : 1 - Math.pow(-2 * t + 2, 2) / 2 }; function useNumberTicker({ value, startValue = 0, delay = 0, decimalPlaces = 0, speed = 1, easing = "ease-out", animate = true, onCompleted }) { const [displayValue, setDisplayValue] = React.useState(startValue); const [isAnimating, setIsAnimating] = React.useState(false); const animationRef = React.useRef(null); const delayTimerRef = React.useRef(null); const startTimeRef = React.useRef(0); const animationCompletedRef = React.useRef(false); const animateRef = React.useRef(animate); const isFirstRenderRef = React.useRef(true); const prevValueRef = React.useRef(value); const prevStartValueRef = React.useRef(startValue); const formatNumber = (num) => { return Intl.NumberFormat("en-US", { minimumFractionDigits: decimalPlaces, maximumFractionDigits: decimalPlaces }).format(num); }; const animateFrame = (timestamp, from, to) => { if (!startTimeRef.current) { startTimeRef.current = timestamp; } const elapsed = timestamp - startTimeRef.current; const duration = 1e3 / speed; const progress = Math.min(elapsed / duration, 1); const easingFunction = easingFunctions[easing] || easingFunctions["ease-out"]; const easedProgress = easingFunction(progress); const valueRange = to - from; const currentValue = from + valueRange * easedProgress; setDisplayValue(currentValue); if (progress < 1) { animationRef.current = requestAnimationFrame((time) => animateFrame(time, from, to)); } else { setDisplayValue(to); setIsAnimating(false); startTimeRef.current = 0; animationCompletedRef.current = true; if (onCompleted) { onCompleted(); } } }; const cleanupAnimation = () => { if (animationRef.current) { cancelAnimationFrame(animationRef.current); animationRef.current = null; } if (delayTimerRef.current) { clearTimeout(delayTimerRef.current); delayTimerRef.current = null; } }; const startAnimationFrom = (fromValue, toValue) => { if (isAnimating && toValue === value && fromValue === displayValue) return; cleanupAnimation(); startTimeRef.current = 0; animationCompletedRef.current = false; setIsAnimating(true); delayTimerRef.current = setTimeout(() => { animationRef.current = requestAnimationFrame( (time) => animateFrame(time, fromValue, toValue) ); }, delay * 1e3); }; const start = () => { animationCompletedRef.current = false; startAnimationFrom(startValue, value); }; const stop = () => { cleanupAnimation(); setIsAnimating(false); }; const reset = () => { stop(); setDisplayValue(startValue); animationCompletedRef.current = false; }; React.useEffect(() => { if (isFirstRenderRef.current) { setDisplayValue(startValue); isFirstRenderRef.current = false; if (animate) { startAnimationFrom(startValue, value); } } }, []); React.useEffect(() => { if (animate !== animateRef.current) { animateRef.current = animate; if (animate) { animationCompletedRef.current = false; cleanupAnimation(); setDisplayValue(startValue); startAnimationFrom(startValue, value); } else if (isAnimating) { stop(); } } }, [animate, value, startValue, isAnimating]); React.useEffect(() => { const valueChanged = value !== prevValueRef.current; const startValueChanged = startValue !== prevStartValueRef.current; prevValueRef.current = value; prevStartValueRef.current = startValue; if (valueChanged || startValueChanged) { if (isAnimating) { startAnimationFrom(displayValue, value); } else if (animationCompletedRef.current && valueChanged) { if (animate) { startAnimationFrom(displayValue, value); } } else if (animate) { startAnimationFrom(startValue, value); } else { setDisplayValue(startValue); } } }, [value, startValue, animate, isAnimating, displayValue]); React.useEffect(() => { return () => { cleanupAnimation(); }; }, []); return { text: formatNumber(displayValue), displayValue, start, stop, reset, isAnimating }; } exports.useNumberTicker = useNumberTicker; //# sourceMappingURL=use-number-ticker.cjs.map