Command Palette

Search for a command to run...

Docs
Scroll Image Tunnel

Scroll Image Tunnel

A pinned photo stage where each scroll-linked image grows from a small point in the center, developing from an oversaturated, high-contrast state into the true image as it settles.

Installation

Usage

import { ScrollImageTunnel } from "@/components/ruixen/scroll-image-tunnel";
 
export default function Page() {
  return (
    <ScrollImageTunnel
      images={[
        { src: "/photos/01.jpg", alt: "Digital architecture" },
        { src: "/photos/02.jpg", alt: "Horizon beyond" },
        { src: "/photos/03.jpg", alt: "Sound wave circuit" },
        { src: "/photos/04.jpg", alt: "Light writer" },
        { src: "/photos/05.jpg", alt: "Star explorer" },
      ]}
    />
  );
}

The section pins for images.length * stepHeight of scroll. Each photo owns an even slice of that scroll range: it's invisible until its own slice begins, then grows from a small point in the center out to the full frame. Never blurred — instead it starts punchy and oversaturated, like an overdeveloped print, and grades down into the true, correctly balanced image as it finishes growing. Photos that haven't reached their slice yet stay fully hidden, so nothing lingers behind the one currently in view.

Inside a bounded container

Pass a ref to a fixed-height self-scrolling ancestor via container, and shrink stepHeight to match — scroll progress is measured against that ref instead of the page.

import { useRef } from "react";
import { ScrollImageTunnel } from "@/components/ruixen/scroll-image-tunnel";
 
export default function Preview() {
  const ref = useRef<HTMLDivElement>(null);
  return (
    <div ref={ref} className="h-[600px] overflow-y-auto rounded-xl">
      <ScrollImageTunnel images={images} container={ref} stepHeight="150vh" />
    </div>
  );
}

Notes

  • The pinned stage uses bg-background, so it adapts to light and dark theme like the rest of the site. Override via className for a different backdrop.
  • Honors prefers-reduced-motion: renders a plain static stack of the photos with no pinning or scroll-driven transforms.

Props

PropTypeDefaultDescription
images{ src: string; alt: string }[]-Photos shown in sequence, one per scroll segment.
hintReactNode"Scroll down to reveal the images"Hint shown above the pinned stage before the user starts scrolling.
stepHeightstring"200vh"Scroll distance dedicated to each photo. Taller = slower reveal.
containerRefObject<HTMLElement | null>-Scrollable ancestor to track instead of the page — for embedding in a bounded panel.
classNamestring-Extra classes merged onto the root container.