58 lines
1.4 KiB
TypeScript
58 lines
1.4 KiB
TypeScript
|
|
"use client";
|
||
|
|
import { useEffect } from "react";
|
||
|
|
import gsap from "gsap";
|
||
|
|
import { ScrollTrigger } from "gsap/ScrollTrigger";
|
||
|
|
import { SplitText } from "gsap/SplitText";
|
||
|
|
|
||
|
|
gsap.registerPlugin(ScrollTrigger, SplitText);
|
||
|
|
|
||
|
|
export const useSplitHeading = (
|
||
|
|
headingRef: React.RefObject<HTMLElement | null>,
|
||
|
|
subHeadingRef?: React.RefObject<HTMLElement | null>
|
||
|
|
) => {
|
||
|
|
useEffect(() => {
|
||
|
|
if (!headingRef.current) return;
|
||
|
|
|
||
|
|
const headingSplit = new SplitText(headingRef.current, { type: "words" });
|
||
|
|
const subSplit =
|
||
|
|
subHeadingRef?.current &&
|
||
|
|
new SplitText(subHeadingRef.current, { type: "words" });
|
||
|
|
|
||
|
|
// heading animation
|
||
|
|
gsap.from(headingSplit.words, {
|
||
|
|
yPercent: 100,
|
||
|
|
opacity: 0,
|
||
|
|
duration: 1,
|
||
|
|
ease: "expo.out",
|
||
|
|
stagger: 0.07,
|
||
|
|
scrollTrigger: {
|
||
|
|
trigger: headingRef.current,
|
||
|
|
start: "top 85%",
|
||
|
|
toggleActions: "play none none none",
|
||
|
|
},
|
||
|
|
});
|
||
|
|
|
||
|
|
// subheading animation
|
||
|
|
if (subSplit) {
|
||
|
|
gsap.from(subSplit.words, {
|
||
|
|
yPercent: 100,
|
||
|
|
opacity: 0,
|
||
|
|
duration: 1,
|
||
|
|
ease: "expo.out",
|
||
|
|
stagger: 0.04,
|
||
|
|
scrollTrigger: {
|
||
|
|
trigger: subHeadingRef?.current,
|
||
|
|
start: "top 85%",
|
||
|
|
toggleActions: "play none none none",
|
||
|
|
},
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
return () => {
|
||
|
|
headingSplit.revert();
|
||
|
|
subSplit?.revert();
|
||
|
|
ScrollTrigger.getAll().forEach((st) => st.kill());
|
||
|
|
};
|
||
|
|
}, [headingRef, subHeadingRef]);
|
||
|
|
};
|