Docs
Swipe Grid

Swipe Grid

On Scroll Swipe Grid

Scroll Down

image
image
image
image
image
image
image
image
image
image
image
image
image
image
image
image
image
image
image
image
image
image
image
image
image
image
image
image
image
image
image
image
image
image
image
image
image
image
image
image

Refresh The Page For Different Image Positions

Installation

Install additional dependencies

npm i gsap @gsap/react

Copy and paste the following code into your project.

components/edil-ozi/swipe-grid.tsx
"use client";
import Image from "next/image";
import { useEffect, useRef } from "react";
 
import gsap from "gsap";
import { ScrollTrigger } from "gsap/dist/ScrollTrigger";
 
//optional hook for smooth scrolling
import useLenis from "@/hooks/useLenis";
 
const images = [
  "https://images.unsplash.com/photo-1576831371356-d6e9411ae501?q=40&w=640",
  "https://images.unsplash.com/photo-1454117096348-e4abbeba002c?q=80&w=640",
  "https://images.unsplash.com/photo-1572756317709-fe9c15ced298?q=80&w=640",
  "https://images.unsplash.com/photo-1595839072560-bac90c191b40?q=80&w=640",
  "https://images.unsplash.com/photo-1588594276800-2de0522b3b73?q=80&w=640",
  "https://images.unsplash.com/photo-1572756317709-fe9c15ced298?q=80&w=640",
  "https://images.unsplash.com/photo-1482053450283-3e0b78b09a70?q=80&w=640",
  "https://images.unsplash.com/photo-1491895200222-0fc4a4c35e18?q=80&w=640",
];
 
const SwipeGrid = () => {
  const grid = useRef<any>(null);
  const gridWrap = useRef<any>(null);
 
  const hasRun = useRef(false);
 
  const applyAnimation = () => {
    // Register Scroll Triggren
    gsap.registerPlugin(ScrollTrigger);
 
    // Child elements of grid
    const gridItems = grid.current?.querySelectorAll(".grid__item");
    const gridItemsInner = [...gridItems].map((item) => item.querySelector(".grid__item-inner"));
 
    // Define GSAP timeline with ScrollTrigger
    const timeline = gsap.timeline({
      defaults: { ease: "none" },
      scrollTrigger: {
        trigger: gridWrap.current,
        start: "top bottom+=5%",
        end: "bottom top-=5%",
        scrub: true,
        // markers: true // Optional: for debugging
      },
    });
 
    grid.current.style.perspective = "1000px";
    grid.current.style.width = "calc(1 / 0.65 * 100%)";
    grid.current.style.height = "calc(1 / 0.5 * 100%)";
 
    timeline
      .set(gridWrap.current, {
        rotationY: 25,
      })
      .set(gridItems, {
        z: () => gsap.utils.random(-1600, 200),
      })
      .fromTo(
        gridItems,
        { xPercent: () => gsap.utils.random(-1000, -500) },
        { xPercent: () => gsap.utils.random(500, 1000) },
        0,
      )
      .fromTo(gridItemsInner, { scale: 2 }, { scale: 0.5 }, 0);
  };
 
  useLenis();
 
  useEffect(() => {
    //make sure we run this function only once
    if (!hasRun.current && grid.current) {
      applyAnimation();
      window.scrollTo({ top: 0 });
      hasRun.current = true;
    }
  }, [grid]);
 
  return (
    <div className="z-10 w-full overflow-hidden bg-stone-200 dark:bg-stone-900">
      <h1 className="h-[50%] py-20 text-center text-4xl">Scroll Down</h1>
      <section className="relative mb-[20vh]">
        <div
          ref={grid}
          className="grid h-[calc(1/1*100%)] w-[calc(1/1*100%)] place-items-center p-8"
          style={{ perspective: "1500px" }}
        >
          <div
            style={{ transformStyle: "preserve-3d" }}
            ref={gridWrap}
            className="grid h-auto w-full grid-cols-4 gap-[2vw]"
          >
            {Array(5)
              .fill(images)
              .flat()
              .map((src, index) => (
                <div
                  key={index}
                  className="grid__item relative grid aspect-[1.5] h-auto w-full place-items-center overflow-hidden rounded-md ring-1"
                >
                  <Image
                    objectFit="cover"
                    quality={40}
                    src={src}
                    fill={true}
                    className="grid__item-inner relative h-auto min-w-[300px]"
                    alt="image"
                  />
                </div>
              ))}
          </div>
        </div>
      </section>
      <h2 className="pb-20 text-center text-2xl text-gray-500 dark:text-gray-600">
        Refresh The Page For Different Image Positions
      </h2>
    </div>
  );
};
 
export default SwipeGrid;

Credits

This component is inspired by Codrops