import { useLayoutEffect } from "react"; import { gsap } from "gsap"; import { ScrollTrigger } from "gsap/ScrollTrigger"; import { isPageTransitionActive } from "../transitions/PageTransition"; let pluginsRegistered = false; const registerGsap = () => { if (!pluginsRegistered) { gsap.registerPlugin(ScrollTrigger); pluginsRegistered = true; } }; const createRevealWords = (element) => { if (!element) { return []; } if (element.dataset.revealPrepared === "true") { return Array.from(element.querySelectorAll(".reveal-word")); } const originalHtml = element.innerHTML; const segments = originalHtml .split(//i) .map((segment) => segment.trim()) .filter(Boolean); if (segments.length === 0) { return []; } element.dataset.revealPrepared = "true"; element.dataset.revealOriginalHtml = originalHtml; element.innerHTML = segments .map((segment) => { const words = segment .split(/\s+/) .filter(Boolean) .map( (word) => `${word}` ) .join(" "); return `${words}`; }) .join(""); return Array.from(element.querySelectorAll(".reveal-word")); }; const restoreRevealWords = (element) => { if (!element || element.dataset.revealPrepared !== "true") { return; } const originalHtml = element.dataset.revealOriginalHtml; if (originalHtml) { element.innerHTML = originalHtml; } delete element.dataset.revealPrepared; delete element.dataset.revealOriginalHtml; }; function collectRevealItems(scope) { const groups = gsap.utils.toArray("[data-reveal-group]", scope); return groups.map((group) => ({ group, items: Array.from(group.querySelectorAll("[data-reveal]")).filter( (item) => item.closest("[data-reveal-group]") === group ), })); } function hideRevealItems(scope) { collectRevealItems(scope).forEach(({ items }) => { items.forEach((item) => { if (item.dataset.reveal === "lines") { gsap.set(item, { autoAlpha: 0 }); } else { gsap.set(item, { y: 36, autoAlpha: 0, force3D: true }); } }); }); } function setupReveals(scope) { const preparedElements = []; const ctx = gsap.context(() => { const entries = collectRevealItems(scope); entries.forEach(({ group, items }) => { if (items.length === 0) { return; } items.forEach((item) => { if (item.dataset.reveal === "lines") { gsap.set(item, { autoAlpha: 0 }); return; } gsap.set(item, { y: 36, autoAlpha: 0, force3D: true, }); }); ScrollTrigger.create({ trigger: group, start: group.dataset.revealStart || "top 78%", once: true, onEnter: () => { const timeline = gsap.timeline(); items.forEach((item, index) => { const position = index === 0 ? 0 : "<0.16"; if (item.dataset.reveal === "lines") { const words = createRevealWords(item); if (words.length === 0) { return; } preparedElements.push(item); gsap.set(item, { autoAlpha: 1 }); gsap.set(words, { yPercent: 115, rotate: 2.2, transformOrigin: "0% 100%", force3D: true, }); timeline.to( words, { yPercent: 0, rotate: 0, duration: 1.08, stagger: 0.065, ease: "power4.out", clearProps: "transform", }, position ); return; } timeline.to( item, { y: 0, autoAlpha: 1, duration: 1.02, ease: "power4.out", clearProps: "transform,opacity,visibility", }, position ); }); }, }); }); ScrollTrigger.refresh(); }, scope); return { ctx, preparedElements }; } function useScrollTextReveal(scopeRef, dependencyKey = "") { useLayoutEffect(() => { const scope = scopeRef.current; if (!scope || typeof window === "undefined") { return undefined; } if (window.matchMedia("(prefers-reduced-motion: reduce)").matches) { return undefined; } registerGsap(); let result = null; let handleReady = null; if (isPageTransitionActive()) { hideRevealItems(scope); handleReady = () => { result = setupReveals(scope); }; window.addEventListener("page-transition-ready", handleReady, { once: true }); } else { result = setupReveals(scope); } return () => { if (handleReady) { window.removeEventListener("page-transition-ready", handleReady); } if (result) { result.ctx.revert(); result.preparedElements.forEach((element) => restoreRevealWords(element)); } }; }, [scopeRef, dependencyKey]); } export default useScrollTextReveal;