Parallax Grid Scroll
A grid where two columns scroll in oposite directions, giving a parallax effect.
Installation
Install dependencies
npm i framer-motion clsx tailwind-merge
Add util file
lib/utils.ts
import { ClassValue, clsx } from "clsx";
import { twMerge } from "tailwind-merge";
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
}
Copy the source code
components/ui/parallax-scroll.tsx
"use client";
import { useScroll, useTransform } from "framer-motion";
import { useRef } from "react";
import { motion } from "framer-motion";
import Image from "next/image";
import { cn } from "@/lib/utils";
export const ParallaxScroll = ({
images,
className,
}: {
images: string[];
className?: string;
}) => {
const gridRef = useRef<any>(null);
const { scrollYProgress } = useScroll({
container: gridRef, // remove this if your container is not fixed height
offset: ["start start", "end start"], // remove this if your container is not fixed height
});
const translateFirst = useTransform(scrollYProgress, [0, 1], [0, -200]);
const translateSecond = useTransform(scrollYProgress, [0, 1], [0, 200]);
const translateThird = useTransform(scrollYProgress, [0, 1], [0, -200]);
const third = Math.ceil(images.length / 3);
const firstPart = images.slice(0, third);
const secondPart = images.slice(third, 2 * third);
const thirdPart = images.slice(2 * third);
return (
<div
className={cn("h-[40rem] items-start overflow-y-auto w-full", className)}
ref={gridRef}
>
<div
className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 items-start max-w-5xl mx-auto gap-10 py-40 px-10"
ref={gridRef}
>
<div className="grid gap-10">
{firstPart.map((el, idx) => (
<motion.div
style={{ y: translateFirst }} // Apply the translateY motion value here
key={"grid-1" + idx}
>
<Image
src={el}
className="h-80 w-full object-cover object-left-top rounded-lg gap-10 !m-0 !p-0"
height="400"
width="400"
alt="thumbnail"
/>
</motion.div>
))}
</div>
<div className="grid gap-10">
{secondPart.map((el, idx) => (
<motion.div style={{ y: translateSecond }} key={"grid-2" + idx}>
<Image
src={el}
className="h-80 w-full object-cover object-left-top rounded-lg gap-10 !m-0 !p-0"
height="400"
width="400"
alt="thumbnail"
/>
</motion.div>
))}
</div>
<div className="grid gap-10">
{thirdPart.map((el, idx) => (
<motion.div style={{ y: translateThird }} key={"grid-3" + idx}>
<Image
src={el}
className="h-80 w-full object-cover object-left-top rounded-lg gap-10 !m-0 !p-0"
height="400"
width="400"
alt="thumbnail"
/>
</motion.div>
))}
</div>
</div>
</div>
);
};
Example
Copy the source code
components/ui/parallax-scroll-2.tsx
"use client";
import { useScroll, useTransform } from "framer-motion";
import { useRef } from "react";
import { motion } from "framer-motion";
import Image from "next/image";
import { cn } from "@/lib/utils";
export const ParallaxScrollSecond = ({
images,
className,
}: {
images: string[];
className?: string;
}) => {
const gridRef = useRef<any>(null);
const { scrollYProgress } = useScroll({
container: gridRef, // remove this if your container is not fixed height
offset: ["start start", "end start"], // remove this if your container is not fixed height
});
const translateYFirst = useTransform(scrollYProgress, [0, 1], [0, -200]);
const translateXFirst = useTransform(scrollYProgress, [0, 1], [0, -200]);
const rotateXFirst = useTransform(scrollYProgress, [0, 1], [0, -20]);
const translateYThird = useTransform(scrollYProgress, [0, 1], [0, -200]);
const translateXThird = useTransform(scrollYProgress, [0, 1], [0, 200]);
const rotateXThird = useTransform(scrollYProgress, [0, 1], [0, 20]);
const third = Math.ceil(images.length / 3);
const firstPart = images.slice(0, third);
const secondPart = images.slice(third, 2 * third);
const thirdPart = images.slice(2 * third);
return (
<div
className={cn("h-[40rem] items-start overflow-y-auto w-full", className)}
ref={gridRef}
>
<div
className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 items-start max-w-5xl mx-auto gap-10 py-40 px-10"
ref={gridRef}
>
<div className="grid gap-10">
{firstPart.map((el, idx) => (
<motion.div
style={{
y: translateYFirst,
x: translateXFirst,
rotateZ: rotateXFirst,
}} // Apply the translateY motion value here
key={"grid-1" + idx}
>
<Image
src={el}
className="h-80 w-full object-cover object-left-top rounded-lg gap-10 !m-0 !p-0"
height="400"
width="400"
alt="thumbnail"
/>
</motion.div>
))}
</div>
<div className="grid gap-10">
{secondPart.map((el, idx) => (
<motion.div key={"grid-2" + idx}>
<Image
src={el}
className="h-80 w-full object-cover object-left-top rounded-lg gap-10 !m-0 !p-0"
height="400"
width="400"
alt="thumbnail"
/>
</motion.div>
))}
</div>
<div className="grid gap-10">
{thirdPart.map((el, idx) => (
<motion.div
style={{
y: translateYThird,
x: translateXThird,
rotateZ: rotateXThird,
}}
key={"grid-3" + idx}
>
<Image
src={el}
className="h-80 w-full object-cover object-left-top rounded-lg gap-10 !m-0 !p-0"
height="400"
width="400"
alt="thumbnail"
/>
</motion.div>
))}
</div>
</div>
</div>
);
};
Props
Prop name | Type | Description |
---|---|---|
className | string | The class name of the child component. |
images | string[] | The array of image URLs for the parallax scroll |