Text Parallax

Parallax effect on text based on scroll interactions

You are just as likely to solve a problem by being unconventional and determined as by being brilliant.

You are just as likely to solve a problem by being unconventional and determined as by being brilliant.

You are just as likely to solve a problem by being unconventional and determined as by being brilliant.

Implementation

Component for the Text Parallax.

text-parallax.tsx - typescript
import {useEffect, useRef} from 'react';
import {cn} from '@core/lib/utils';
import {textParallaxScroll} from '@core/lib/animations';

interface ParallaxArgs{
    text: string,
    direction?: string
}

const TextContent:ParallaxArgs[] = [
    {
        text: "You are just as likely to solve a problem by being unconventional and determined as by being brilliant.",
        direction: "-10%"
    },
    {
        text: "You are just as likely to solve a problem by being unconventional and determined as by being brilliant.",
        direction: "15%"
    },
    {
        text: "You are just as likely to solve a problem by being unconventional and determined as by being brilliant.",
        direction: "-20%"
    },
]

export const TextParallax = ({
    className
}:{
    className?: string
}) => {
  const triggerRef = useRef<HTMLDivElement>(null);
  const paragraphRefs = useRef<(HTMLElement | null)[]>([]);

  useEffect(() => {
    const trigger = triggerRef.current;

    if (trigger && paragraphRefs.current.length > 0) {
      paragraphRefs.current.forEach((paragraph) => {
        if (!paragraph) return; 
        const direction = paragraph.dataset.direction;
        textParallaxScroll({
          select: paragraph,
          direction: direction,
          trigger: trigger,
          start: '0'
        });
      });
    }
  }, []); 

  return (
    <div
      id="trigger"
      ref={triggerRef} 
      className={cn('text-nowrap w-full py-20 overflow-hidden flex justify-center items-center flex-col gap-2 uppercase', className)}
    >
      {TextContent.map((content, index)=>{
        return(
          <p
          key={index}
          className="text-5xl"
          ref={(el) => (paragraphRefs.current[index] = el)}
          data-direction={content.direction}
          >
            {content.text}
          </p>
        )
      })}
            
    </div>
  );
};

Install GSAP into your project.

GSAP is a lightweight animation library that will help animating things easier.
Run this command in the terminal with your package manager of choice to install GSAP, npm will be used as an example.

install-gsap - text
npm i gsap

Update astro.config.mjs.

Update astro.config.mjs as GSAP RegisterPlugin might break imports if you're using astro on top

astro.config.mjs - javascript
// astro.config.mjs
import { defineConfig } from 'astro/config';
import tailwind from '@astrojs/tailwind';
import react from '@astrojs/react';

export default defineConfig({
  integrations: [tailwind(), react()],

  vite: {
    ssr: {
      noExternal: ['gsap'], 
    },
  },
});

Copy and paste the following code into your project.

A provider to easily initilize plugins beforehand for GSAP

animations/gsap.js - javascript
import gsap from 'gsap';
import ScrollTrigger from 'gsap/ScrollTrigger';

class GsapProvider {
    gsap;
    constructor() {
        gsap.registerPlugin(ScrollTrigger);
        this.gsap = gsap;
    }
}

export const gsapProvider = new GsapProvider();

Copy and paste the following code into your project.

Function that applies the animation using GSAP or you could customize this with vanillajs.

animations/animations.ts - typescript
import { gsapProvider } from "./gsap";

export function textParallaxScroll({
  select,
  trigger,
  direction = "10%",
  start,
  end
}: {
  select: Element;
  trigger: Element; // trigger to start the animation
  direction?: string;
  start?: string, // starting point of animation
  end?: string // end point of animation
}) {
  gsapProvider.gsap.to(select, {
    x: direction,
    scrollTrigger: {
      trigger: trigger,
      start: start ?? "top top",
      end: end ?? "bottom top",
      scrub: true,
    },

  });
}

Update imports.

Change the import paths to match your project.