Parallax Image for React

Scroll-linked parallax effect for images or content. Moves children on scroll for depth.

Component

↓ scroll

Mountain lake
Forest path
Desert dunes
Ocean waves
Snowy peaks
Autumn valley
Tropical beach
Foggy hills
Canyon view
Rice terraces
Northern lights
Waterfall
Lavender field
Rocky coast
Misty jungle
Salt flats

Installation

 

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

All props are optional unless marked required. Use these to customize every aspect of the component.

PropTypeDefaultDescription
childrenReact.ReactNode-Content to apply parallax to (typically an image).
classNamestring-Additional CSS classes for the container.
childrenClassNamestring-Classes for the inner content wrapper (the moving layer).
intensitynumber50Parallax movement in pixels. Higher = more movement.
containerRefReact.RefObject<HTMLElement | null>undefinedRef to a scrollable overflow container. Omit to track window scroll instead.

Accessibility

  • Decorative visual effect with no interactive controls, no focusable elements, and no keyboard handlers of its own.
  • Adds no ARIA roles or labels; any accessible name or alt text must come from the children passed in, such as the wrapped img.
  • Does not check prefers-reduced-motion or matchMedia, so the scroll-linked motion runs for everyone; it should be guarded to disable or reduce the transform for users who prefer reduced motion.
  • Because the moving content is absolutely positioned and over-sized, ensure wrapped images keep meaningful alt text since the wrapper itself conveys no semantics.

Performance

  • Scroll movement is driven entirely by a Framer Motion y motion value rendered as a translateY transform, which is GPU-compositable and avoids layout and paint work.
  • The static top, bottom, left, and right offsets are written once as inline styles to over-size the layer and are not animated, so only the cheap transform changes during scroll.
  • A single scroll listener is registered as passive and is cleaned up on unmount or when containerRef, intensity, or y change, preventing leaked listeners.
  • The update handler runs synchronously on every scroll event and calls getBoundingClientRect, a forced layout read, without requestAnimationFrame throttling, which can be costly on rapid scrolling.
  • There is no IntersectionObserver or visibility gating and no will-change hint, so the handler keeps running and reading layout even when the element is off-screen.

Examples

Single image

Wrap a single image for a scroll-driven parallax effect.

↓ scroll

Mountain lake
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

Mountain lake
Forest path
Desert dunes
Ocean waves
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>
  );
}

Last updated on Jun 19

Made with ❤️ by Pulkit &

© 2026 Pulkit. All rights reserved

Last updated: