Marquee With GSAP

A marquee created using GSAP which can change direction according to scroll.

framer-motion

Frankly, my dear, I don't give a damn

Frankly, my dear, I don't give a damn

Installation

Install the required dependencies:

npm install framer-motion
components/ui/marquee-with-gsap.tsx
"use client";
import gsap from "gsap";
import { ScrollTrigger } from "gsap/ScrollTrigger";
import { useEffect, useRef, useState } from "react";

type componentProp = {
  text?: string;
  top?: string;
  size?: number;
};

const Marquee = ({ text, size,top="" }: componentProp) => {
  const [hover, setHover] = useState(false);

  const firstText = useRef<HTMLParagraphElement | null>(null);
  const secondText = useRef<HTMLParagraphElement | null>(null);

  const xPercent = useRef(0);
  const direction = useRef(-1);
  const animationFrame = useRef<number | null>(null);

  useEffect(() => {
    gsap.registerPlugin(ScrollTrigger);

    const animate = () => {
      if (xPercent.current > 0) xPercent.current = -100;
      if (xPercent.current < -100) xPercent.current = 0;

      gsap.set([firstText.current, secondText.current], {
        xPercent: xPercent.current,
      });

      xPercent.current += 0.1 * direction.current;

      animationFrame.current = requestAnimationFrame(animate);
    };

    if (!hover) {
      animationFrame.current = requestAnimationFrame(animate);
    }

    return () => {
      if (animationFrame.current) {
        cancelAnimationFrame(animationFrame.current);
      }
    };
  }, [hover]);

  useEffect(() => {
    const trigger = ScrollTrigger.create({
      trigger: document.documentElement,
      start: 0,
      end: window.innerHeight,
      scrub: 0.25,
      onUpdate: (self) => {
        direction.current = self.direction * -1;
      },
    });

    return () => {
      trigger.kill();
    };
  }, []);

  return (
    <div className="font-bebas absolute overflow-hidden w-full h-full cursor-default">
      <div
        className={`relative ${top} w-full font-extrabold md:text-[2rem] lg:text-[3rem] text-[1rem]`}
        style={{ fontSize: size ? `${size}rem` : "" }}
      >
        <p
          ref={firstText}
          onMouseEnter={() => setHover(true)}
          onMouseLeave={() => setHover(false)}
        >
          {text}
        </p>
        <p
          ref={secondText}
          onMouseEnter={() => setHover(true)}
          onMouseLeave={() => setHover(false)}
          className="absolute top-0 left-full w-full"
        >
          {text}
        </p>
      </div>
    </div>
  );
};

export default Marquee;