Parallax Image
Scroll-linked parallax effect for images or content. Moves children on scroll for depth.
Component
↓ scroll
















"use client";
import { motion, useMotionValue } from "framer-motion";
import { useEffect, useRef } from "react";
import { cn } from "@/lib/utils";
interface ParallaxImageProps {
children: React.ReactNode;
className?: string;
intensity?: number;
containerRef?: React.RefObject<HTMLElement | null>;
}
export function ParallaxImage({
children,
className,
intensity = 50,
containerRef,
}: ParallaxImageProps) {
const ref = useRef<HTMLDivElement>(null);
const y = useMotionValue(0);
useEffect(() => {
const el = ref.current;
if (!el) {
return;
}
const scrollEl: HTMLElement | Window =
containerRef?.current ?? window;
const update = () => {
const containerTop = containerRef?.current
? containerRef.current.getBoundingClientRect().top
: 0;
const containerHeight = containerRef?.current
? containerRef.current.clientHeight
: window.innerHeight;
const rect = el.getBoundingClientRect();
const elCenter =
rect.top + rect.height / 2 - containerTop;
const normalized =
(elCenter / containerHeight - 0.5) * 2;
y.set(normalized * intensity);
};
scrollEl.addEventListener("scroll", update, {
passive: true,
});
update();
return () =>
scrollEl.removeEventListener("scroll", update);
}, [containerRef, intensity, y]);
return (
<motion.div
ref={ref}
className={cn("relative overflow-hidden", className)}
>
<motion.div
style={{
bottom: -intensity,
left: 0,
position: "absolute",
right: 0,
top: -intensity,
y,
}}
className="[&_img]:size-full [&_img]:object-cover"
>
{children}
</motion.div>
</motion.div>
);
}Installation
pnpm dlx shadcn@latest add "https://pulkitxm.com/components/parallax-image.json"1. Install dependencies
pnpm add framer-motion2. Copy the component file
"use client";
import { motion, useMotionValue } from "framer-motion";
import { useEffect, useRef } from "react";
import { cn } from "@/lib/utils";
interface ParallaxImageProps {
children: React.ReactNode;
className?: string;
intensity?: number;
containerRef?: React.RefObject<HTMLElement | null>;
}
export function ParallaxImage({
children,
className,
intensity = 50,
containerRef,
}: ParallaxImageProps) {
const ref = useRef<HTMLDivElement>(null);
const y = useMotionValue(0);
useEffect(() => {
const el = ref.current;
if (!el) {
return;
}
const scrollEl: HTMLElement | Window =
containerRef?.current ?? window;
const update = () => {
const containerTop = containerRef?.current
? containerRef.current.getBoundingClientRect().top
: 0;
const containerHeight = containerRef?.current
? containerRef.current.clientHeight
: window.innerHeight;
const rect = el.getBoundingClientRect();
const elCenter =
rect.top + rect.height / 2 - containerTop;
const normalized =
(elCenter / containerHeight - 0.5) * 2;
y.set(normalized * intensity);
};
scrollEl.addEventListener("scroll", update, {
passive: true,
});
update();
return () =>
scrollEl.removeEventListener("scroll", update);
}, [containerRef, intensity, y]);
return (
<motion.div
ref={ref}
className={cn("relative overflow-hidden", className)}
>
<motion.div
style={{
bottom: -intensity,
left: 0,
position: "absolute",
right: 0,
top: -intensity,
y,
}}
className="[&_img]:size-full [&_img]:object-cover"
>
{children}
</motion.div>
</motion.div>
);
}3. Import and use
import { ParallaxImage } from "@/components/parallax-image";
<ParallaxImage>
<img src="/hero.jpg" alt="Hero" />
</ParallaxImage>;Usage
Import
Add the ParallaxImage import.
import { ParallaxImage } from "@/components/parallax-image";Use
Wrap your image or content.
<ParallaxImage>
<img src="/hero.jpg" alt="Hero" />
</ParallaxImage>;Guidelines
- Give the container a height (e.g. min-h-48, h-64) for the effect to be visible.
- Use intensity to control how far the content moves (default 50px).
- Works with images or any content; images get object-cover by default.
Props
| Prop | Type | Default | Description |
|---|---|---|---|
| children | React.ReactNode | — | Content to apply parallax to (typically an image). |
| className | string | — | Additional CSS classes for the container. |
| intensity | number | 50 | Parallax movement in pixels. Higher = more movement. |
| containerRef | React.RefObject<HTMLElement | null> | undefined | Ref to a scrollable overflow container. Omit to track window scroll instead. |
Examples
Single image
Wrap a single image for a scroll-driven parallax effect.
↓ scroll

import { ParallaxImage } from "@/components/parallax-image";
export function ParallaxBasic() {
return (
<ParallaxImage className="h-56 w-full">
<img
src="/hero.jpg"
alt="Hero"
className="size-full object-cover"
/>
</ParallaxImage>
);
}With container ref
Pass containerRef when scrolling inside an overflow container instead of the window.
↓ scroll




import { useRef } from "react";
import { ParallaxImage } from "@/components/parallax-image";
const IMAGES = [
{ src: "/img-1.jpg", alt: "Mountain lake" },
{ src: "/img-2.jpg", alt: "Forest path" },
{ src: "/img-3.jpg", alt: "Desert dunes" },
{ src: "/img-4.jpg", alt: "Ocean waves" },
];
export function ParallaxGrid() {
const containerRef = useRef<HTMLDivElement>(null);
return (
<div
ref={containerRef}
className="h-96 overflow-y-auto"
>
<div className="grid grid-cols-2 gap-2 p-2">
{IMAGES.map((img) => (
<ParallaxImage
key={img.alt}
className="h-36 rounded-md"
intensity={40}
containerRef={containerRef}
>
<img
src={img.src}
alt={img.alt}
className="size-full object-cover"
/>
</ParallaxImage>
))}
</div>
</div>
);
}