import { useLayoutEffect } from "react"; import { gsap } from "gsap"; import { ScrollTrigger } from "gsap/ScrollTrigger"; let pluginsRegistered = false; const registerGsap = () => { if (!pluginsRegistered) { gsap.registerPlugin(ScrollTrigger); pluginsRegistered = true; } }; const createRevealLines = (element) => { if (!element) { return []; } if (element.dataset.revealPrepared === "true") { return Array.from(element.querySelectorAll(".reveal-line")); } 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) => `${segment}` ) .join(""); return Array.from(element.querySelectorAll(".reveal-line")); }; const restoreRevealLines = (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 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(); const preparedElements = []; const ctx = gsap.context(() => { const groups = gsap.utils.toArray("[data-reveal-group]"); groups.forEach((group) => { const items = Array.from(group.querySelectorAll("[data-reveal]")).filter( (item) => item.closest("[data-reveal-group]") === group ); 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 86%", once: true, onEnter: () => { const timeline = gsap.timeline(); items.forEach((item, index) => { const position = index === 0 ? 0 : "<0.16"; if (item.dataset.reveal === "lines") { const lines = createRevealLines(item); if (lines.length === 0) { return; } preparedElements.push(item); gsap.set(item, { autoAlpha: 1 }); gsap.set(lines, { yPercent: 115, rotate: 2.2, transformOrigin: "0% 100%", force3D: true, }); timeline.to( lines, { yPercent: 0, rotate: 0, duration: 1.18, stagger: 0.1, 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.revert(); preparedElements.forEach((element) => restoreRevealLines(element)); }; }, [scopeRef, dependencyKey]); } export default useScrollTextReveal;