Image Parallax

  • React
  • Typescript
  • GSAP
  • ScrollTrigger Plugin
  • useGSAP Plugin
  • Tailwind CSS
components/image-parallax.tsx
import gsap from "gsap";
import { useGSAP } from "@gsap/react";
import { ScrollTrigger } from "gsap/ScrollTrigger";

import { useRef } from "react";

gsap.registerPlugin(useGSAP, ScrollTrigger);

export interface ImageParallaxProps {
  src: string;
  alt: string;
  intensity?: number;
  imageScale?: number;
  containerClassName?: string;
  imageClassName?: string;
}

const DEFAULT_INTENSITY = 20;
const DEFAULT_IMAGE_SCALE = 1.5;

export function ImageParallax({
  src,
  alt,
  intensity = DEFAULT_INTENSITY,
  imageScale = DEFAULT_IMAGE_SCALE,
  containerClassName,
  imageClassName,
}: ImageParallaxProps) {
  const containerRef = useRef<HTMLDivElement>(null);
  const imageRef = useRef<HTMLImageElement>(null);

  useGSAP(() => {
    const image = imageRef.current;
    if (!image) return;

    gsap.fromTo(
      image,
      { yPercent: -intensity },
      {
        yPercent: intensity,
        ease: "none",
        scrollTrigger: {
          trigger: containerRef.current,
          scrub: true,
          start: "top bottom",
          end: "bottom top",
        },
      },
    );
  }, [intensity]);

  return (
    <div
      ref={containerRef}
      className={`overflow-hidden ${containerClassName ?? ""}`.trim()}
    >
      <img
        ref={imageRef}
        className={imageClassName}
        style={{ transform: `scale(${imageScale})` }}
        src={src}
        alt={alt}
      />
    </div>
  );
}

<ImageParallax
  src="https://images.unsplash.com/photo-1769708526202-247e1fe5691f"
  alt="Yosemite National Park"
  intensity={30}
/>
Yosemite National Park