UNPKG

react-gsap-animation-library

Version:

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

187 lines (167 loc) 6.17 kB
import React, { useRef, useEffect } from 'react'; import { useGSAP } from '@gsap/react'; import gsap from 'gsap'; import { useAnimationSettings } from '../../context/AnimationContext'; /** * TypingText component for creating typing text animations * * @param {Object} props - Component props * @param {string|string[]} props.text - Text to type or array of texts to type in sequence * @param {number} props.typingSpeed - Typing speed in characters per second * @param {number} props.deleteSpeed - Delete speed in characters per second * @param {number} props.delayBetweenTexts - Delay between texts in seconds (when text is an array) * @param {number} props.startDelay - Delay before typing starts in seconds * @param {boolean} props.cursorBlink - Whether to blink the cursor * @param {string} props.cursorChar - Character to use as cursor * @param {string} props.cursorColor - Color of the cursor * @param {string} props.trigger - Animation trigger type ('load', 'scroll', 'none') * @param {number} props.threshold - Viewport threshold to trigger animation (0-1) * @param {number} props.loop - Number of times to loop the animation (Infinity for infinite) * @param {function} props.onComplete - Callback when animation completes * @param {string} props.className - Additional CSS class names * @param {Object} props.style - Custom styles */ const TypingText = ({ text = '', typingSpeed = 10, deleteSpeed = 5, delayBetweenTexts = 1.5, startDelay = 0.5, cursorBlink = true, cursorChar = '|', cursorColor = 'inherit', trigger = 'load', threshold = 0.2, loop = 0, onComplete = () => {}, className = '', style = {}, ...otherProps }) => { // Get reference to the DOM element const containerRef = useRef(null); const textRef = useRef(null); const cursorRef = useRef(null); const { disableAllAnimations, disableScrollAnimations } = useAnimationSettings(); // Convert text to array if it's a string const textArray = Array.isArray(text) ? text : [text]; // UseGSAP hook useGSAP(() => { if (!containerRef.current || !textRef.current || !cursorRef.current) return; // Skip if animations disabled if (disableAllAnimations || (trigger === 'scroll' && disableScrollAnimations)) { // Just show the full text textRef.current.textContent = textArray[textArray.length - 1] || ''; cursorRef.current.style.opacity = 0; return; } // Only set up automatic animations for load and scroll triggers if (trigger !== 'load' && trigger !== 'scroll') return; const typeText = (index = 0, charIndex = 0, isDeleting = false) => { const currentText = textArray[index % textArray.length] || ''; if (!isDeleting && charIndex <= currentText.length) { // Typing textRef.current.textContent = currentText.substring(0, charIndex); charIndex++; gsap.delayedCall(1 / typingSpeed, () => { typeText(index, charIndex, isDeleting); }); } else if (isDeleting && charIndex >= 0) { // Deleting textRef.current.textContent = currentText.substring(0, charIndex); charIndex--; gsap.delayedCall(1 / deleteSpeed, () => { typeText(index, charIndex, isDeleting); }); } else if (!isDeleting && charIndex > currentText.length) { // Finished typing, wait before deleting if (textArray.length > 1 || loop !== 0) { gsap.delayedCall(delayBetweenTexts, () => { typeText(index, charIndex, true); }); } else { // Only one text and no loop, we're done if (typeof onComplete === 'function') onComplete(); } } else if (isDeleting && charIndex < 0) { // Finished deleting, move to next text const nextIndex = index + 1; if (nextIndex < textArray.length || (loop === Infinity || nextIndex < textArray.length * (loop + 1))) { gsap.delayedCall(startDelay, () => { typeText(nextIndex, 0, false); }); } else { // Finished all texts and loops if (typeof onComplete === 'function') onComplete(); } } }; // Set up cursor blinking if (cursorBlink) { gsap.to(cursorRef.current, { opacity: 0, duration: 0.5, repeat: -1, yoyo: true }); } // Start typing after delay gsap.delayedCall(startDelay, () => { typeText(0, 0, false); }); // Set up scroll trigger if needed if (trigger === 'scroll') { const timeline = gsap.timeline({ paused: true }); ScrollTrigger.create({ trigger: containerRef.current, start: `top bottom-=${threshold * 100}%`, onEnter: () => timeline.play() }); } return () => { // Clear all delayed calls and animations gsap.killTweensOf(textRef.current); gsap.killTweensOf(cursorRef.current); // Clear any ScrollTrigger instances if (trigger === 'scroll') { ScrollTrigger.getAll() .filter(st => st.vars.trigger === containerRef.current) .forEach(st => st.kill()); } }; }, { scope: containerRef, dependencies: [ JSON.stringify(textArray), typingSpeed, deleteSpeed, delayBetweenTexts, startDelay, cursorBlink, trigger, threshold, loop, disableAllAnimations, disableScrollAnimations ] }); // Cursor styles const cursorStyle = { color: cursorColor, display: 'inline-block', marginLeft: '0.1em', fontWeight: 'normal' }; return ( <div ref={containerRef} className={`react-gsap-typing-text ${className}`} style={style} {...otherProps} > <span ref={textRef}></span> <span ref={cursorRef} style={cursorStyle}>{cursorChar}</span> </div> ); }; export default TypingText;