UNPKG

react-gsap-animation-library

Version:

A comprehensive React component library that leverages GSAP for animations with official GSAP-React integration

311 lines (273 loc) 8.83 kB
import { useRef } from 'react'; import { useGSAP } from '@gsap/react'; import gsap from 'gsap'; import ScrollTrigger from 'gsap/ScrollTrigger'; import { useAnimationSettings } from '../context/AnimationContext'; // Make sure GSAP plugins are registered if (typeof window !== 'undefined') { try { gsap.registerPlugin(useGSAP, ScrollTrigger); } catch (error) { console.warn("GSAP plugin registration failed:", error); } } /** * Primary animation hook that leverages GSAP's official React integration * * @param {Object} options - Configuration options * @param {Object} options.scope - Container element ref for scoping animations * @param {boolean} options.revertOnUpdate - Whether to revert animations when dependencies change * @returns {Object} Animation utilities including tween, timeline, and contextSafe */ export const useAnimation = (options = {}) => { const { disableAllAnimations } = useAnimationSettings(); const containerRef = options.scope || useRef(null); const { contextSafe, context, revert, kill } = useGSAP({ scope: containerRef }); // Create a timeline within the current GSAP context const timeline = (timelineOptions = {}) => { if (disableAllAnimations) return gsap.timeline(); return gsap.timeline(timelineOptions, context); }; // Create a tween within the current GSAP context const tween = (targets, vars) => { if (disableAllAnimations) return { kill: () => {} }; return gsap.to(targets, vars, context); }; // Add animation to a timeline const add = (timeline, animation, position) => { if (disableAllAnimations) return timeline; return timeline.add(animation, position); }; return { containerRef, contextSafe, timeline, tween, add, context, revert, kill }; }; /** * Hook for creating common animation effects with simpler API * * @param {React.RefObject} elementRef - Reference to the element to animate * @param {Object} options - Animation options * @param {string} options.effect - Animation effect ('fadeIn', 'fadeOut', 'slideIn', etc.) * @param {string} options.direction - Animation direction ('up', 'down', 'left', 'right') * @param {number} options.duration - Animation duration in seconds * @param {number} options.delay - Delay before animation starts in seconds * @param {string} options.ease - GSAP easing function * @param {string} options.trigger - Animation trigger ('load', 'scroll', 'none') * @param {number} options.threshold - Viewport threshold for scroll animations (0-1) * @returns {Object} Animation utilities */ export const useAnimationEffect = (elementRef, options = {}) => { const { effect = 'fadeIn', direction = 'up', distance = 30, duration = 0.8, delay = 0, ease = 'power3.out', trigger = 'scroll', threshold = 0.2, onComplete } = options; const { disableAllAnimations, disableScrollAnimations } = useAnimationSettings(); // Use GSAP's useGSAP for proper context management useGSAP(() => { if (!elementRef?.current || disableAllAnimations || (trigger === 'scroll' && disableScrollAnimations)) { return; } // Configure animation based on effect type let animation; // Set initial state and animation based on effect switch (effect) { case 'fadeIn': const fromProps = { opacity: 0 }; if (direction === 'up') fromProps.y = distance; else if (direction === 'down') fromProps.y = -distance; else if (direction === 'left') fromProps.x = distance; else if (direction === 'right') fromProps.x = -distance; animation = gsap.from(elementRef.current, { ...fromProps, duration, delay, ease, onComplete, clearProps: 'all' }); break; case 'fadeOut': const toProps = { opacity: 0 }; if (direction === 'up') toProps.y = -distance; else if (direction === 'down') toProps.y = distance; else if (direction === 'left') toProps.x = -distance; else if (direction === 'right') toProps.x = distance; animation = gsap.to(elementRef.current, { ...toProps, duration, delay, ease, onComplete }); break; case 'slideIn': const slideProps = { opacity: 1 }; const slideFrom = {}; if (direction === 'up') slideFrom.y = distance; else if (direction === 'down') slideFrom.y = -distance; else if (direction === 'left') slideFrom.x = -distance; else if (direction === 'right') slideFrom.x = distance; gsap.set(elementRef.current, { ...slideFrom, opacity: 0 }); animation = gsap.to(elementRef.current, { ...slideProps, x: 0, y: 0, duration, delay, ease, onComplete, clearProps: 'all' }); break; // Add more effect types as needed default: console.warn(`Unknown animation effect: ${effect}`); return; } // If scroll trigger is enabled, create a scroll trigger if (trigger === 'scroll' && !disableScrollAnimations) { animation.pause(); ScrollTrigger.create({ trigger: elementRef.current, start: `top bottom-=${threshold * 100}%`, onEnter: () => animation.play(), once: true }); } }, { scope: elementRef, dependencies: [ effect, direction, distance, duration, delay, ease, trigger, threshold, disableAllAnimations, disableScrollAnimations ] }); return { ref: elementRef }; }; /** * Hook to create scroll-based animations with GSAP ScrollTrigger * * @param {React.RefObject} elementRef - Reference to the element to animate * @param {Object} options - ScrollTrigger options * @param {Object|Function} options.animation - Animation properties or function returning them * @param {Object} options.initial - Initial state properties * @param {string} options.start - ScrollTrigger start position * @param {string} options.end - ScrollTrigger end position * @param {boolean} options.scrub - Whether to link animation progress to scroll position * @param {boolean} options.markers - Show debug markers * @param {boolean} options.pin - Pin the element during animation * @param {string} options.toggleActions - ScrollTrigger toggle actions * @returns {Object} ScrollTrigger utilities */ export const useScrollTrigger = (elementRef, options = {}) => { const { animation, initial, start = 'top bottom-=10%', end = 'bottom top+=10%', scrub = false, markers = false, pin = false, toggleActions = 'play none none reset', onEnter, onLeave, onEnterBack, onLeaveBack } = options; const { disableAllAnimations, disableScrollAnimations } = useAnimationSettings(); useGSAP(() => { if (!elementRef?.current || disableAllAnimations || disableScrollAnimations) { return; } // Set initial state if provided if (initial) { gsap.set(elementRef.current, initial); } // Create animation let tween; if (typeof animation === 'function') { tween = animation(elementRef.current); } else if (animation) { tween = gsap.to(elementRef.current, animation); } else { tween = gsap.to(elementRef.current, { opacity: 1, y: 0, duration: 1 }); } // Pause animation if not in scrub mode if (!scrub) { tween.pause(); } // Create scroll trigger ScrollTrigger.create({ trigger: elementRef.current, start, end, markers, scrub, pin, toggleActions, onEnter: onEnter || (scrub ? null : () => tween.play()), onLeave, onEnterBack, onLeaveBack }); }, { scope: elementRef, dependencies: [ JSON.stringify(animation), JSON.stringify(initial), start, end, scrub, markers, pin, toggleActions, disableAllAnimations, disableScrollAnimations ] }); return { ref: elementRef }; }; export default { useAnimation, useAnimationEffect, useScrollTrigger };