DocDoeAI / src /components /reactbits /ScrollFloat.tsx
asnannp's picture
deploy: sync backend to Space root (learn-lesson HF cache fix)
65d68f5
Raw History Blame Contribute Delete
2.71 kB
"use client";
import { useEffect, useMemo, useRef } from "react";
import { gsap } from "gsap";
import { ScrollTrigger } from "gsap/ScrollTrigger";
import "./ScrollFloat.css";
type ScrollFloatProps = {
children: string;
scrollContainerRef?: React.RefObject<HTMLElement | null>;
containerClassName?: string;
textClassName?: string;
animationDuration?: number;
ease?: string;
scrollStart?: string;
scrollEnd?: string;
stagger?: number;
};
export default function ScrollFloat({
children,
scrollContainerRef,
containerClassName = "",
textClassName = "",
animationDuration = 1,
ease = "back.inOut(2)",
scrollStart = "center bottom+=50%",
scrollEnd = "bottom bottom-=40%",
stagger = 0.03,
}: ScrollFloatProps) {
const containerRef = useRef<HTMLHeadingElement | null>(null);
const splitText = useMemo(
() =>
children.split(" ").map((word, wordIndex, words) => (
<span className="scroll-float-word" key={`${word}-${wordIndex}`}>
{word.split("").map((char, charIndex) => (
<span className="char" key={`${char}-${charIndex}`}>
{char}
</span>
))}
{wordIndex < words.length - 1 && <span className="char">&nbsp;</span>}
</span>
)),
[children],
);
useEffect(() => {
if (typeof window === "undefined") return;
gsap.registerPlugin(ScrollTrigger);
const el = containerRef.current;
if (!el) return;
const reduceMotion = window.matchMedia("(prefers-reduced-motion: reduce)").matches;
const charElements = el.querySelectorAll(".char");
if (reduceMotion) {
gsap.set(charElements, { opacity: 1, yPercent: 0, scaleY: 1, scaleX: 1 });
return;
}
const scroller = scrollContainerRef?.current || window;
const tween = gsap.fromTo(
charElements,
{
willChange: "opacity, transform",
opacity: 0,
yPercent: 120,
scaleY: 2.3,
scaleX: 0.7,
transformOrigin: "50% 0%",
},
{
duration: animationDuration,
ease,
opacity: 1,
yPercent: 0,
scaleY: 1,
scaleX: 1,
stagger,
scrollTrigger: {
trigger: el,
scroller,
start: scrollStart,
end: scrollEnd,
scrub: true,
},
},
);
return () => {
tween.scrollTrigger?.kill();
tween.kill();
};
}, [animationDuration, ease, scrollContainerRef, scrollEnd, scrollStart, stagger]);
return (
<h2 ref={containerRef} className={`scroll-float ${containerClassName}`.trim()}>
<span className={`scroll-float-text ${textClassName}`.trim()}>{splitText}</span>
</h2>
);
}