UNPKG

@jurneyx2/react-lottie-hooks

Version:

🎯 Simple and powerful React hooks for DotLottie animations with GSAP ScrollTrigger integration and automatic SSR/CSR detection

719 lines (713 loc) β€’ 25.1 kB
import { useState, useRef, useCallback, useEffect } from 'react'; import { useGSAP } from '@gsap/react'; import { ScrollTrigger } from 'gsap/ScrollTrigger'; import { gsap } from 'gsap'; // src/useLottieScrollTrigger.ts // src/language.ts var DEBUG_LANGUAGE = { ko: { // λ‘œλ”© κ΄€λ ¨ loading: "GSAP \uBC0F ScrollTrigger \uB77C\uC774\uBE0C\uB7EC\uB9AC\uB97C \uB85C\uB4DC\uD558\uB294 \uC911...", loadSuccess: "GSAP \uB77C\uC774\uBE0C\uB7EC\uB9AC \uB85C\uB4DC \uC644\uB8CC", loadError: "GSAP \uB77C\uC774\uBE0C\uB7EC\uB9AC \uB85C\uB4DC \uC2E4\uD328:", loadComplete: "Lottie \uC560\uB2C8\uBA54\uC774\uC158 \uB85C\uB4DC \uC644\uB8CC", // Lottie μ΄ˆκΈ°ν™” lottieInit: "Lottie \uC560\uB2C8\uBA54\uC774\uC158 \uCD08\uAE30\uD654 \uC911...", lottieSuccess: "Lottie \uC560\uB2C8\uBA54\uC774\uC158 \uCD08\uAE30\uD654 \uC644\uB8CC", lottieError: "Lottie \uC560\uB2C8\uBA54\uC774\uC158 \uCD08\uAE30\uD654 \uC2E4\uD328:", // DotLottie μ „μš© dotLottieSet: "DotLottie \uC778\uC2A4\uD134\uC2A4 \uC124\uC815\uB428:", // ScrollTrigger κ΄€λ ¨ scrollTriggerInit: "ScrollTrigger \uC124\uC815 \uC911...", scrollTriggerStart: "ScrollTrigger \uC2DC\uC791", scrollTriggerSuccess: "ScrollTrigger \uC124\uC815 \uC644\uB8CC", scrollTriggerDestroy: "ScrollTrigger \uD574\uC81C\uB428", // 슀크둀 이벀트 scrollEnter: "\uC2A4\uD06C\uB864 \uC601\uC5ED \uC9C4\uC785", scrollLeave: "\uC2A4\uD06C\uB864 \uC601\uC5ED \uBC97\uC5B4\uB0A8", scrollEnterBack: "\uC2A4\uD06C\uB864 \uC601\uC5ED \uC7AC\uC9C4\uC785", scrollLeaveBack: "\uC2A4\uD06C\uB864 \uC601\uC5ED \uC5ED\uBC29\uD5A5 \uBC97\uC5B4\uB0A8", // μ• λ‹ˆλ©”μ΄μ…˜ μ œμ–΄ playSuccess: "\uC560\uB2C8\uBA54\uC774\uC158 \uC7AC\uC0DD \uC131\uACF5", playError: "\uC560\uB2C8\uBA54\uC774\uC158 \uC7AC\uC0DD \uC2E4\uD328:", pauseError: "\uC560\uB2C8\uBA54\uC774\uC158 \uC77C\uC2DC\uC815\uC9C0 \uC2E4\uD328:", // 쑰건 체크 conditionNotMet: "ScrollTrigger \uC0DD\uC131 \uC870\uAC74 \uBBF8\uCDA9\uC871:", // ν™˜κ²½ κ΄€λ ¨ domNotReady: "DOM\uC774 \uC900\uBE44\uB418\uC9C0 \uC54A\uC74C. \uB300\uAE30 \uC911...", ssrDetected: "SSR \uD658\uACBD \uAC10\uC9C0\uB428. \uD074\uB77C\uC774\uC5B8\uD2B8 \uB9C8\uC6B4\uD2B8 \uB300\uAE30\uC911...", frameworkDetected: (framework) => `${framework} \uD504\uB808\uC784\uC6CC\uD06C \uAC10\uC9C0\uB428`, safeDefaultsApplied: "SSR \uD504\uB808\uC784\uC6CC\uD06C\uC6A9 \uC548\uC804\uD55C \uAE30\uBCF8\uAC12 \uC801\uC6A9\uB428", // ν”„λ ˆμž„μ›Œν¬ 감지 frameworkDetectionFailed: "\uD504\uB808\uC784\uC6CC\uD06C \uAC10\uC9C0 \uC2E4\uD328:", frameworkDetectionUpdated: "\uD504\uB808\uC784\uC6CC\uD06C \uAC10\uC9C0 \uC5C5\uB370\uC774\uD2B8\uB428:", frameworkDetectionUpdatedAfterDOM: "DOM \uB85C\uB4DC \uD6C4 \uD504\uB808\uC784\uC6CC\uD06C \uAC10\uC9C0 \uC5C5\uB370\uC774\uD2B8\uB428:", frameworkReDetectionFailed: "\uD504\uB808\uC784\uC6CC\uD06C \uC7AC\uAC10\uC9C0 \uC2E4\uD328:", frameworkReDetectionAfterDOMFailed: "DOM \uB85C\uB4DC \uD6C4 \uD504\uB808\uC784\uC6CC\uD06C \uC7AC\uAC10\uC9C0 \uC2E4\uD328:", gsapInitFailed: "GSAP \uB610\uB294 \uD504\uB808\uC784\uC6CC\uD06C \uAC10\uC9C0 \uCD08\uAE30\uD654 \uC2E4\uD328:", // 초기 ν”„λ ˆμž„μ›Œν¬ 감지 (μ΅œμƒμœ„ μˆ˜μ€€) initialFrameworkDetectionFailed: "\uD504\uB808\uC784\uC6CC\uD06C \uAC10\uC9C0 \uC2E4\uD328:", initialGSAPInitFailed: "GSAP \uB610\uB294 \uD504\uB808\uC784\uC6CC\uD06C \uAC10\uC9C0 \uCD08\uAE30\uD654 \uC2E4\uD328:", // SSR ν™˜κ²½ ssrEnvironmentMessage: "SSR \uD658\uACBD\uC5D0\uC11C\uB294 ScrollTrigger\uB97C \uC0AC\uC6A9\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.", // DotLottie λ‘œλ”© dotLottieNotLoaded: "DotLottie\uAC00 \uC544\uC9C1 \uB85C\uB4DC\uB418\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4.", // 이벀트 둜그 dotLottiePlayEvent: "DotLottie \uC7AC\uC0DD \uC774\uBCA4\uD2B8", dotLottiePauseEvent: "DotLottie \uC77C\uC2DC\uC815\uC9C0 \uC774\uBCA4\uD2B8", dotLottieStopEvent: "DotLottie \uC815\uC9C0 \uC774\uBCA4\uD2B8", // μ œμ–΄ ν•¨μˆ˜ 둜그 playThrottled: "Play \uD638\uCD9C\uC774 throttle\uB418\uC5C8\uC2B5\uB2C8\uB2E4.", pauseThrottled: "Pause \uD638\uCD9C\uC774 throttle\uB418\uC5C8\uC2B5\uB2C8\uB2E4.", stopThrottled: "Stop \uD638\uCD9C\uC774 throttle\uB418\uC5C8\uC2B5\uB2C8\uB2E4.", playExecuted: "Play \uC2E4\uD589\uB428", pauseExecuted: "Pause \uC2E4\uD589\uB428", stopExecuted: "Stop \uC2E4\uD589\uB428", setFrameExecuted: (frame) => `SetFrame \uC2E4\uD589\uB428: ${frame}`, // μ—λŸ¬ λ©”μ‹œμ§€ playExecutionError: "Play \uC2E4\uD589 \uC911 \uC624\uB958:", pauseExecutionError: "Pause \uC2E4\uD589 \uC911 \uC624\uB958:", stopExecutionError: "Stop \uC2E4\uD589 \uC911 \uC624\uB958:", setFrameExecutionError: "SetFrame \uC2E4\uD589 \uC911 \uC624\uB958:", // GSAP μ• λ‹ˆλ©”μ΄μ…˜ gsapAnimationExecution: (trigger) => `GSAP \uC560\uB2C8\uBA54\uC774\uC158 \uC2E4\uD589: ${trigger}` }, en: { // Loading related loading: "Loading GSAP and ScrollTrigger libraries...", loadSuccess: "GSAP libraries loaded successfully", loadError: "Failed to load GSAP libraries:", loadComplete: "Lottie animation loaded successfully", // Lottie initialization lottieInit: "Initializing Lottie animation...", lottieSuccess: "Lottie animation initialized successfully", lottieError: "Failed to initialize Lottie animation:", // DotLottie specific dotLottieSet: "DotLottie instance set:", // ScrollTrigger related scrollTriggerInit: "Setting up ScrollTrigger...", scrollTriggerStart: "ScrollTrigger started", scrollTriggerSuccess: "ScrollTrigger setup complete", scrollTriggerDestroy: "ScrollTrigger destroyed", // Scroll events scrollEnter: "Entered scroll area", scrollLeave: "Left scroll area", scrollEnterBack: "Re-entered scroll area", scrollLeaveBack: "Left scroll area backwards", // Animation control playSuccess: "Animation played successfully", playError: "Failed to play animation:", pauseError: "Failed to pause animation:", // 쑰건 체크 conditionNotMet: "ScrollTrigger creation conditions not met:", // ν™˜κ²½ κ΄€λ ¨ domNotReady: "DOM not ready. Waiting...", ssrDetected: "SSR environment detected. Waiting for client mount...", frameworkDetected: (framework) => `${framework} framework detected`, safeDefaultsApplied: "Safe defaults applied for SSR framework", // ν”„λ ˆμž„μ›Œν¬ 감지 frameworkDetectionFailed: "Framework detection failed:", frameworkDetectionUpdated: "Framework detection updated:", frameworkDetectionUpdatedAfterDOM: "Framework detection updated after DOM loaded:", frameworkReDetectionFailed: "Framework re-detection failed:", frameworkReDetectionAfterDOMFailed: "Framework re-detection after DOM loaded failed:", gsapInitFailed: "GSAP or framework detection initialization failed:", // 초기 ν”„λ ˆμž„μ›Œν¬ 감지 (μ΅œμƒμœ„ μˆ˜μ€€) initialFrameworkDetectionFailed: "Framework detection failed:", initialGSAPInitFailed: "GSAP or framework detection initialization failed:", // SSR ν™˜κ²½ ssrEnvironmentMessage: "ScrollTrigger cannot be used in SSR environment.", // DotLottie λ‘œλ”© dotLottieNotLoaded: "DotLottie is not loaded yet.", // 이벀트 둜그 dotLottiePlayEvent: "DotLottie play event", dotLottiePauseEvent: "DotLottie pause event", dotLottieStopEvent: "DotLottie stop event", // μ œμ–΄ ν•¨μˆ˜ 둜그 playThrottled: "Play call was throttled.", pauseThrottled: "Pause call was throttled.", stopThrottled: "Stop call was throttled.", playExecuted: "Play executed", pauseExecuted: "Pause executed", stopExecuted: "Stop executed", setFrameExecuted: (frame) => `SetFrame executed: ${frame}`, // μ—λŸ¬ λ©”μ‹œμ§€ playExecutionError: "Error during play execution:", pauseExecutionError: "Error during pause execution:", stopExecutionError: "Error during stop execution:", setFrameExecutionError: "Error during setFrame execution:", // GSAP μ• λ‹ˆλ©”μ΄μ…˜ gsapAnimationExecution: (trigger) => `GSAP animation execution: ${trigger}` } }; var language_default = DEBUG_LANGUAGE; // src/utils/detectSSRFramework.ts var detectSSRFramework = () => { const isClient2 = typeof window !== "undefined"; if (!isClient2) { return { isNextJS: false, isRemix: false, isSSRFramework: false }; } try { const isNextJS = !!window.__NEXT_DATA__; let isRemix = false; if (document.readyState !== "loading") { isRemix = !!(window.__remixContext || window.__remixRouteModules || window.__remixManifest || document.querySelector("script[data-remix]") || document.querySelector("[data-remix-root]")); } return { isNextJS, isRemix, isSSRFramework: isNextJS || isRemix }; } catch (error) { const tempMsg = language_default["ko"]; console.warn(tempMsg.initialFrameworkDetectionFailed, error); return { isNextJS: false, isRemix: false, isSSRFramework: false }; } }; var isClient = typeof window !== "undefined"; // src/useLottieScrollTrigger.ts var detectedFramework = { isNextJS: false, isRemix: false, isSSRFramework: false }; if (isClient) { try { detectedFramework = detectSSRFramework(); gsap.registerPlugin(ScrollTrigger); } catch (error) { const tempMsg = language_default["ko"]; console.warn(tempMsg.initialGSAPInitFailed, error); } } var { isSSRFramework } = detectedFramework; var useLottieScrollTrigger = (options = {}) => { if (!isClient) { const safeRefs = { triggerRef: { current: null }, lottieContainerRef: { current: null } }; return { ...safeRefs, isMounted: false, isDOMReady: false, isClient: false, isLoaded: false, handleDotLottieRef: () => { }, dotLottie: null, isDotLottieLoaded: false, play: () => { }, pause: () => { }, stop: () => { }, setFrame: () => { }, getCurrentFrame: () => 0, getIsPlaying: () => false, isPlaying: false, currentFrame: 0, isSSRFramework: false }; } const { start = "top center", end = "bottom 20%", markers = process.env.NODE_ENV === "development", pauseOnLoad = true, debug = false, debugLanguage = "ko", strictMode = isSSRFramework, waitForDOMReady = isSSRFramework, // Performance optimization options enableStateTracking = false, // Default: false (performance first) frameUpdateThrottle = 100, onPlayStateChange, onFrameChange, // DotLottie callbacks onEnter, onLeave, onEnterBack, onLeaveBack, // Common options gsapAnimations, scrollTriggerOptions } = options; const [isMounted, setIsMounted] = useState(false); const [isDOMReady, setIsDOMReady] = useState(false); const triggerRef = useRef(null); const lottieContainerRef = useRef(null); const [dotLottie, setDotLottie] = useState(null); const [isDotLottieLoaded, setIsDotLottieLoaded] = useState(false); const playStateRef = useRef(false); const frameRef = useRef(0); const [isPlaying, setIsPlaying] = useState(false); const [currentFrame, setCurrentFrame] = useState(0); const lastUpdateTimeRef = useRef(0); const lastPlayTimeRef = useRef(0); const lastPauseTimeRef = useRef(0); const getCurrentFrame = useCallback(() => frameRef.current, []); const getIsPlaying = useCallback(() => playStateRef.current, []); const msg = language_default[debugLanguage] || language_default["ko"]; useEffect(() => { if (isClient) { setIsMounted(true); if (waitForDOMReady) { const checkDOMReady = () => { if (document.readyState === "complete" || document.readyState === "interactive") { setIsDOMReady(true); try { const redetected = detectSSRFramework(); if (debug && redetected.isSSRFramework !== detectedFramework.isSSRFramework) { console.log(msg.frameworkDetectionUpdated, redetected); } } catch (error) { if (debug) console.warn(msg.frameworkReDetectionFailed, error); } } else { const handleDOMContentLoaded = () => { setIsDOMReady(true); try { const redetected = detectSSRFramework(); if (debug && redetected.isSSRFramework !== detectedFramework.isSSRFramework) { console.log( msg.frameworkDetectionUpdatedAfterDOM, redetected ); } } catch (error) { if (debug) console.warn(msg.frameworkReDetectionAfterDOMFailed, error); } }; const handleLoad = () => setIsDOMReady(true); document.addEventListener( "DOMContentLoaded", handleDOMContentLoaded ); window.addEventListener("load", handleLoad); return () => { document.removeEventListener( "DOMContentLoaded", handleDOMContentLoaded ); window.removeEventListener("load", handleLoad); }; } }; checkDOMReady(); } else { setIsDOMReady(true); } } }, [waitForDOMReady, debug]); const handleDotLottieRef = useCallback( (dotLottieInstance) => { if (dotLottieInstance) { if (debug) console.log(msg.dotLottieSet, dotLottieInstance); setDotLottie(dotLottieInstance); const handleLoad = () => { if (debug) console.log(msg.loadComplete); setIsDotLottieLoaded(true); if (pauseOnLoad) { setTimeout(() => { dotLottieInstance.pause(); }, 100); } }; const handleLoadError = (error) => { console.error(msg.loadError, error); }; const handlePlay = () => { playStateRef.current = true; if (enableStateTracking) { setIsPlaying(true); } onPlayStateChange?.(true); if (debug) console.log(msg.dotLottiePlayEvent); }; const handlePause = () => { playStateRef.current = false; if (enableStateTracking) { setIsPlaying(false); } onPlayStateChange?.(false); if (debug) console.log(msg.dotLottiePauseEvent); }; const handleStop = () => { playStateRef.current = false; frameRef.current = 0; if (enableStateTracking) { setIsPlaying(false); setCurrentFrame(0); } onPlayStateChange?.(false); onFrameChange?.(0); if (debug) console.log(msg.dotLottieStopEvent); }; const handleFrame = (event) => { const now = Date.now(); const newFrame = Math.round(event.currentFrame || 0); frameRef.current = newFrame; if (now - lastUpdateTimeRef.current > frameUpdateThrottle) { if (enableStateTracking) { setCurrentFrame(newFrame); } onFrameChange?.(newFrame); lastUpdateTimeRef.current = now; } }; dotLottieInstance.addEventListener("load", handleLoad); dotLottieInstance.addEventListener("loadError", handleLoadError); dotLottieInstance.addEventListener("play", handlePlay); dotLottieInstance.addEventListener("pause", handlePause); dotLottieInstance.addEventListener("stop", handleStop); if (debug || enableStateTracking || onFrameChange) { dotLottieInstance.addEventListener("frame", handleFrame); } if (dotLottieInstance.isLoaded) { handleLoad(); } const initialPlaying = dotLottieInstance.isPlaying || false; const initialFrame = Math.round(dotLottieInstance.currentFrame || 0); playStateRef.current = initialPlaying; frameRef.current = initialFrame; if (enableStateTracking) { setIsPlaying(initialPlaying); setCurrentFrame(initialFrame); } return () => { dotLottieInstance.removeEventListener("load", handleLoad); dotLottieInstance.removeEventListener("loadError", handleLoadError); dotLottieInstance.removeEventListener("play", handlePlay); dotLottieInstance.removeEventListener("pause", handlePause); dotLottieInstance.removeEventListener("stop", handleStop); if (debug || enableStateTracking || onFrameChange) { dotLottieInstance.removeEventListener("frame", handleFrame); } }; } else { setDotLottie(null); setIsDotLottieLoaded(false); setIsPlaying(false); setCurrentFrame(0); } }, [debug, pauseOnLoad, msg] ); const executeGsapAnimation = useCallback( (trigger) => { if (!isClient || !gsapAnimations || !lottieContainerRef.current) return; const { rotation = 0, scale = 1, x = 0, y = 0, opacity = 1, duration = 1, ease = "power2.out", trigger: animationTrigger = "enter" } = gsapAnimations; if (animationTrigger === trigger) { if (debug) console.log(msg.gsapAnimationExecution(trigger), gsapAnimations); gsap.to(lottieContainerRef.current, { rotation, scale, x, y, opacity, duration, ease }); } }, [gsapAnimations, debug] ); const defaultOnEnter = useCallback( (dotLottie2) => { if (debug) console.log(msg.scrollEnter); try { dotLottie2.setFrame(0); dotLottie2.play(); if (debug) console.log(msg.playSuccess); } catch (error) { console.error(msg.playError, error); } }, [debug, msg] ); const defaultOnLeave = useCallback( (dotLottie2) => { if (debug) console.log(msg.scrollLeave); try { dotLottie2.pause(); } catch (error) { console.error(msg.pauseError, error); } }, [debug, msg] ); const defaultOnEnterBack = useCallback( (dotLottie2) => { if (debug) console.log(msg.scrollEnterBack); try { dotLottie2.play(); } catch (error) { console.error(msg.playError, error); } }, [debug, msg] ); const defaultOnLeaveBack = useCallback( (dotLottie2) => { if (debug) console.log(msg.scrollLeaveBack); try { dotLottie2.pause(); } catch (error) { console.error(msg.pauseError, error); } }, [debug, msg] ); const createScrollTriggerHandlers = useCallback(() => { if (dotLottie) { return { onEnter: () => { const handler = onEnter || defaultOnEnter; handler(dotLottie); executeGsapAnimation("enter"); }, onLeave: () => { const handler = onLeave || defaultOnLeave; handler(dotLottie); executeGsapAnimation("leave"); }, onEnterBack: () => { const handler = onEnterBack || defaultOnEnterBack; handler(dotLottie); executeGsapAnimation("enterBack"); }, onLeaveBack: () => { const handler = onLeaveBack || defaultOnLeaveBack; handler(dotLottie); executeGsapAnimation("leaveBack"); } }; } return {}; }, [ dotLottie, onEnter, onLeave, onEnterBack, onLeaveBack, defaultOnEnter, defaultOnLeave, defaultOnEnterBack, defaultOnLeaveBack, executeGsapAnimation ]); useGSAP(() => { if (!isClient) { if (debug) console.log(msg.ssrEnvironmentMessage); return; } const isAnimationReady = dotLottie && isDotLottieLoaded; const basicConditions = isMounted && isAnimationReady && triggerRef.current; const strictConditions = strictMode ? isDOMReady && document.readyState === "complete" : true; if (!basicConditions || !strictConditions) { if (debug) { console.log(msg.conditionNotMet, { isMounted, isAnimationReady, triggerRef: !!triggerRef.current, ...strictMode && { isDOMReady, documentReady: document.readyState === "complete" } }); } return; } if (debug) console.log(msg.scrollTriggerStart); if (ScrollTrigger) { ScrollTrigger.refresh(); } const handlers = createScrollTriggerHandlers(); const scrollTriggerConfig = { trigger: triggerRef.current, start, end, markers, ...handlers, ...scrollTriggerOptions || {} }; if (gsapAnimations?.scrub !== void 0) { scrollTriggerConfig.scrub = gsapAnimations.scrub; } const trigger = ScrollTrigger.create(scrollTriggerConfig); return () => { trigger.kill(); if (debug) console.log(msg.scrollTriggerDestroy); }; }, [ isMounted, dotLottie, isDotLottieLoaded, isDOMReady, strictMode, start, end, markers, createScrollTriggerHandlers, debug, msg, scrollTriggerOptions, gsapAnimations ]); const play = useCallback(() => { if (!dotLottie || !isDotLottieLoaded) { if (debug) console.warn(msg.dotLottieNotLoaded); return; } const now = Date.now(); if (now - lastPlayTimeRef.current < 100) { if (debug) console.log(msg.playThrottled); return; } lastPlayTimeRef.current = now; try { dotLottie.play(); playStateRef.current = true; if (enableStateTracking) { setIsPlaying(true); } onPlayStateChange?.(true); if (debug) console.log(msg.playExecuted); } catch (error) { console.error(msg.playExecutionError, error); } }, [ dotLottie, isDotLottieLoaded, enableStateTracking, onPlayStateChange, debug ]); const pause = useCallback(() => { if (!dotLottie || !isDotLottieLoaded) { if (debug) console.warn(msg.dotLottieNotLoaded); return; } const now = Date.now(); if (now - lastPauseTimeRef.current < 100) { if (debug) console.log(msg.pauseThrottled); return; } lastPauseTimeRef.current = now; try { dotLottie.pause(); playStateRef.current = false; if (enableStateTracking) { setIsPlaying(false); } onPlayStateChange?.(false); if (debug) console.log(msg.pauseExecuted); } catch (error) { console.error(msg.pauseExecutionError, error); } }, [ dotLottie, isDotLottieLoaded, enableStateTracking, onPlayStateChange, debug ]); const stop = useCallback(() => { if (!dotLottie || !isDotLottieLoaded) { if (debug) console.warn(msg.dotLottieNotLoaded); return; } const now = Date.now(); if (now - lastPauseTimeRef.current < 100) { if (debug) console.log(msg.stopThrottled); return; } lastPauseTimeRef.current = now; try { dotLottie.stop(); playStateRef.current = false; frameRef.current = 0; if (enableStateTracking) { setIsPlaying(false); setCurrentFrame(0); } onPlayStateChange?.(false); onFrameChange?.(0); if (debug) console.log(msg.stopExecuted); } catch (error) { console.error(msg.stopExecutionError, error); } }, [ dotLottie, isDotLottieLoaded, enableStateTracking, onPlayStateChange, onFrameChange, debug ]); const setFrame = useCallback( (frame) => { if (!dotLottie || !isDotLottieLoaded) { if (debug) console.warn(msg.dotLottieNotLoaded); return; } try { dotLottie.setFrame(frame); frameRef.current = frame; if (enableStateTracking) { setCurrentFrame(frame); } onFrameChange?.(frame); if (debug) console.log(msg.setFrameExecuted(frame)); } catch (error) { console.error(msg.setFrameExecutionError, error); } }, [dotLottie, isDotLottieLoaded, enableStateTracking, onFrameChange, debug] ); const isLoaded = isDotLottieLoaded; return { // Common refs and states triggerRef, lottieContainerRef, // Used for GSAP animations isMounted, isDOMReady, isClient, isLoaded, // DotLottie specific handleDotLottieRef, // Used in DotLottieReact dotLottie, isDotLottieLoaded, // Common control functions play, pause, stop, setFrame, // Ref-based getters (no re-rendering) getCurrentFrame, getIsPlaying, // React state based (updated only when enableStateTracking is true) isPlaying, currentFrame, // Environment info isSSRFramework }; }; export { useLottieScrollTrigger };