Zoom Parallax
A photo group that zooms through with the scroll — each image scales from the center at its own rate, creating a fly-through depth parallax.
npx shadcn@latest add https://webberui.com/r/zoom-parallax.jsonPlayground
Tune the props live — the code snippet updates as you go, so you can dial in the look you want before copying it.
<ZoomParallax />
Installation
npx shadcn@latest add https://webberui.com/r/zoom-parallax.jsonOr, once registries are configured in components.json, install it as @webberui/zoom-parallax.
Usage
import { ZoomParallax } from "@/components/ui/zoom-parallax";
<ZoomParallax
images={[
{ src: "/photos/01.jpg", alt: "Ridgeline" },
{ src: "/photos/02.jpg", alt: "Bay" },
{ src: "/photos/03.jpg", alt: "Forest" },
{ src: "/photos/04.jpg", alt: "Sunrise" },
{ src: "/photos/05.jpg", alt: "City" },
{ src: "/photos/06.jpg", alt: "Lake" },
{ src: "/photos/07.jpg", alt: "Canyon" },
]}
>
<h2 className="text-2xl font-bold text-white">Zoom Parallax</h2>
</ZoomParallax>The component stretches out a scroll track of its own (h-[300vh] by default), and the stage in the middle is pinned sticky across the whole viewport; as you scroll down, every image scales from the center of the stage, with the outer images scaling faster, producing the fly-through parallax.
Nested scroll container
If the scrolling happens inside a container with overflow (rather than the window), pass that container's ref to container and override the track and stage heights with className / stageClassName:
const scrollerRef = React.useRef<HTMLDivElement>(null);
<div ref={scrollerRef} className="h-[320px] overflow-y-auto">
<ZoomParallax
images={images}
container={scrollerRef}
className="h-[960px]" // scroll track = several times the stage height
stageClassName="h-[320px]" // stage = the container's visible height
/>
</div>Props
| Prop | Type | Default | Description |
|---|---|---|---|
images | (string | { src: string; alt?: string })[] | — | Image sources; 5–7 is recommended |
container | RefObject<HTMLElement | null> | — | Ref of the nested scroll container; the window is the scroll container by default |
layout | ZoomParallaxSlot[] | 7 built-in slots | Override the fly-through layout; when there are more images than slots, the slots are reused cyclically |
smooth | boolean | true | Whether to smooth the scroll progress with a spring |
children | ReactNode | — | Content layered over the images, centered and not intercepting the pointer |
aria-label | string | "Zoom parallax gallery" | Accessible name for the gallery |
className | string | h-[300vh] | The outer scroll track; its height determines the scrollable distance |
stageClassName | string | h-screen | The pinned stage; override it with the container height for nested scrolling |
imageClassName | string | — | Applied to each image element |
ZoomParallaxSlot
| Field | Type | Description |
|---|---|---|
scale | number | Target scale factor at the end of the scroll (it always starts at 1) |
width | number | Image width, as a percentage of the stage width |
height | number | Image height, as a percentage of the stage height |
x | number | Horizontal offset from the center of the stage (percentage) |
y | number | Vertical offset from the center of the stage (percentage) |
How it works
- Layout sizes and offsets are all expressed as "percentages of the stage", so the effect adapts whether it is full screen or inside a small nested container.
- Every image binds its own
useTransformthat maps scroll progress onto an independent scaling curve; the outer images get the higher factors and fly apart faster. - Scaling is spring-smoothed by default (
smooth), so it still eases to a stop when the wheel stops rather than snapping hard. - Images map onto
layoutin order; when there are more images than slots, the same set of positions is reused cyclically.
Accessibility
- When the user has "reduce motion" enabled at the system level, all the images are shown as a static grid instead, with no scroll zooming at all.
- The stage container carries
role="group"andaria-label; decorative images with noaltare markedaria-hidden, while images with analtare announced normally.
Image Sequence Scrub
The product-page effect that scrubs a set of frames with scroll progress — the view stays pinned while the imagery plays frame by frame.
Scroll Progress Set
Three scroll indicators in one — a top bar, a bottom-right ring, and section dots on the right, all sharing the same scroll progress.