Download src/components/reactbits/ScrollFloat.tsx from asnannp/DocDoeAI: direct link, hf CLI and curl.
- Browser
- Download file 2.71 kB
-
https://huggingface.co/spaces/asnannp/DocDoeAI/resolve/main/src/components/reactbits/ScrollFloat.tsx
- Command line
-
hf download hf://spaces/asnannp/DocDoeAI/src/components/reactbits/ScrollFloat.tsx
-
curl -L -o ScrollFloat.tsx https://huggingface.co/spaces/asnannp/DocDoeAI/resolve/main/src/components/reactbits/ScrollFloat.tsx
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"> </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> | |
| ); | |
| } | |