Parallax Image for React

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

Component

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.

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.

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 25

Made with ❤️ by Pulkit &

© 2026 Pulkit. All rights reserved

Last updated: