Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 | import { useCallback, useMemo, useRef } from 'react' export interface PulsingAnimationOptions { /** Duration of the animation in milliseconds */ duration: number /** Number of pulses during the animation */ pulses: number /** Called on each animation frame with the pulse progress (0-1) */ onProgress: (pulseProgress: number) => void /** Called when the animation completes naturally (not cancelled) */ onComplete?: () => void } /** * Hook for creating pulsing animations using requestAnimationFrame. * * The pulsing effect uses a sine wave for smooth on/off transitions: * `sin(progress * π * pulses * 2) * 0.5 + 0.5` * * This creates a value that oscillates between 0 and 1 for the specified * number of pulses over the duration. * * @example * ```tsx * const { start, cancel } = usePulsingAnimation() * * useEffect(() => { * start({ * duration: 2000, * pulses: 3, * onProgress: (progress) => setFlashProgress(progress), * onComplete: () => console.log('Animation complete') * }) * return () => cancel() * }, [start, cancel]) * ``` */ export function usePulsingAnimation() { const animationFrameId = useRef<number | null>(null) const isCancelled = useRef(false) const cancel = useCallback(() => { isCancelled.current = true if (animationFrameId.current !== null) { cancelAnimationFrame(animationFrameId.current) animationFrameId.current = null } }, []) const start = useCallback( (options: PulsingAnimationOptions) => { const { duration, pulses, onProgress, onComplete } = options // Cancel any existing animation cancel() // Reset cancellation flag for new animation isCancelled.current = false const startTime = Date.now() const animate = () => { if (isCancelled.current) return const elapsed = Date.now() - startTime const progress = Math.min(elapsed / duration, 1) // Create pulsing effect: sin wave for smooth on/off const pulseProgress = Math.sin(progress * Math.PI * pulses * 2) * 0.5 + 0.5 onProgress(pulseProgress) if (progress < 1) { animationFrameId.current = requestAnimationFrame(animate) } else { animationFrameId.current = null onComplete?.() } } animationFrameId.current = requestAnimationFrame(animate) }, [cancel] ) // Memoize the returned object to prevent re-renders when used as a dependency return useMemo(() => ({ start, cancel }), [start, cancel]) } |