Getting Started
Components
Search for a command to run...
import { ParallaxImageScroll } from "@/registry/tween/parallax-image-scroll"
const sectionClass = "relative w-full overflow-hidden"
const sectionStyle = { height: "var(--preview-h, 100vh)" }
const projectsStyle = { height: "calc(var(--preview-h, 100vh) * 1.25)" }
const imgWrapClass = "absolute inset-0 h-full w-full overflow-hidden"
const heading =
"font-serif text-[clamp(2.5rem,6vw,5rem)] leading-none tracking-[-0.01em] text-[#dbccc0]"
const para =
"text-xs font-medium tracking-[0.08em] text-[#dbccc0] uppercase leading-none"
const buttonClass =
"mx-auto my-1 inline-block rounded-full bg-[#dbccc0] px-3 py-1.5 text-[12px] font-semibold tracking-[0.04em] uppercase text-[#0f0f0f]"
export function ParallaxImageScrollDemo() {
return (
<div className="w-full bg-[#0f0f0f] font-sans">
<section className={sectionClass} style={sectionStyle}>
<div className={imgWrapClass}>
<ParallaxImageScroll src="https://images.unsplash.com/photo-1777223128245-0875df686de5?q=80&w=1200&h=1600&fit=crop&crop=faces,center&auto=format" />
</div>
<div className="absolute top-1/2 left-1/2 z-[2] flex w-full -translate-x-1/2 -translate-y-1/2 items-center justify-between p-4">
<p className={para}>Tween</p>
<p className={para}>Works</p>
<p className={para}>Studio</p>
<p className={para}>2025</p>
</div>
</section>
<section
className="relative flex w-full gap-[10em] overflow-hidden"
style={projectsStyle}
>
<div className={imgWrapClass}>
<ParallaxImageScroll src="https://images.unsplash.com/photo-1529139574466-a303027c1d8b?q=80&w=1200&h=1600&fit=crop&crop=faces,center&auto=format" />
</div>
<div className="absolute top-[35%] left-[35%] z-[2] w-1/4 -translate-x-1/2 -translate-y-1/2">
<p className={para}>
Layers drift at their own pace as you scroll, so the world’s
“depth” builds itself. Every image moves a little slower
than the page, and the scene quietly comes alive.
</p>
</div>
<div className="relative h-1/2 flex-1">
<div className={imgWrapClass}>
<ParallaxImageScroll src="https://images.unsplash.com/photo-1617690032703-f991ed0e0ee6?q=80&w=1200&h=1600&fit=crop&crop=faces,center&auto=format" />
</div>
</div>
<div className="relative flex flex-[2] flex-col items-center justify-center gap-8">
<div className="flex flex-col gap-4 text-center">
<h1 className={heading}>Northwind</h1>
<p className={para}>Brand / Web / Motion</p>
</div>
<div className="flex flex-col gap-4 text-center">
<h1 className={heading}>Glasshouse</h1>
<p className={para}>Brand / Web / Motion</p>
</div>
<div className="flex flex-col gap-4 text-center">
<h1 className={heading}>Tidewater</h1>
<p className={para}>Brand / Web / Motion</p>
</div>
<div className="flex flex-col gap-4 text-center">
<h1 className={heading}>Lantern’s Edge</h1>
<p className={para}>Brand / Web / Motion</p>
</div>
</div>
</section>
<section
className="relative flex w-full overflow-hidden bg-[#68461f]"
style={sectionStyle}
>
<div className="relative flex flex-1 flex-col items-center justify-center p-4">
<p className={`${para} mb-2 w-1/2 text-center underline`}>
Our Approach
</p>
<p className={`${para} w-1/2 text-center`}>
We treat scroll as a timeline. Each section’s
“rhythm” is composed by hand, then layered with parallax
so the whole story unfolds the moment you start to move.
</p>
</div>
<div className="relative flex flex-1 items-center justify-center p-4">
<div className="relative h-full w-full">
<div className={imgWrapClass}>
<ParallaxImageScroll src="https://images.unsplash.com/photo-1644402079748-a3eba5224f29?q=80&w=1200&h=1600&fit=crop&crop=faces,center&auto=format" />
</div>
</div>
</div>
</section>
<section
className={`${sectionClass} flex items-center justify-center`}
style={sectionStyle}
>
<div className={imgWrapClass}>
<ParallaxImageScroll src="https://images.unsplash.com/photo-1727341557146-4abab94d0812?q=80&w=1200&h=1600&fit=crop&crop=faces,center&auto=format" />
</div>
<div className="relative z-[2] text-center">
<p className={para}>Ready when you are</p>
<h1 className={`${heading} uppercase`}>Start a project</h1>
<p className={`${para} mx-auto my-4 w-3/4`}>
Have something with depth in mind? Let’s bring it into motion.
</p>
<button type="button" className={buttonClass}>
Get in touch
</button>
</div>
</section>
<section
className="relative flex w-full overflow-hidden bg-[#68461f] p-4"
style={sectionStyle}
>
<div className="flex h-full flex-[4] flex-col justify-between">
<p className={para}>Index / Site / Map</p>
<div>
<p className={para}>Navigate</p>
<h1 className={`${heading} uppercase`}>Work</h1>
<h1 className={`${heading} uppercase`}>Studio</h1>
<h1 className={`${heading} uppercase`}>Journal</h1>
<h1 className={`${heading} uppercase`}>Contact</h1>
</div>
<p className={para}>© Tween Studio 2025</p>
</div>
<div className="flex h-full flex-[2] flex-col justify-between">
<p className={para}>
Stay in the loop <br />
<button type="button" className={buttonClass}>
Subscribe
</button>
</p>
<div className="relative h-1/2 w-full">
<div className={imgWrapClass}>
<ParallaxImageScroll src="https://images.unsplash.com/photo-1645520941137-7b546caab649?q=80&w=1200&h=1600&fit=crop&crop=faces,center&auto=format" />
</div>
</div>
<p className={para}>Privacy / Terms / Press</p>
</div>
</section>
</div>
)
}
import { ParallaxImageScroll } from "@/components/ui/parallax-image-scroll"<section className="relative h-screen overflow-hidden">
<div className="absolute inset-0">
<ParallaxImageScroll src="/portraits/hero.jpg" />
</div>
</section>Wrap the component in a positioned container with overflow-hidden so the translated image stays clipped to its slot. As the page scrolls, the image moves by speed pixels per scroll pixel and scales from baseScale to activeScale, keeping the edges covered throughout the travel. The motion eases via lerp, so lower values add more inertia and drift.
| Prop | Type | Default | Description |
|---|---|---|---|
src | string | — | Image source. |
alt | string | "" | Alt text. |
speed | number | 0.2 | Parallax factor — how many pixels the image moves per pixel of scroll. |
baseScale | number | 1.25 | Scale applied before the first scroll, to hide edges during translation. |
activeScale | number | 1.5 | Scale applied once scrolling has started. |
lerp | number | 0.1 | Easing factor — lower values produce more inertia. |
scroller | HTMLElement | null | — | Optional explicit scroller; defaults to the ambient PreviewScrollerProvider or window. |
className | string | — | Extra classes on the image element. |
The component reads scroll from the ambient PreviewScrollerProvider, an explicit scroller, or falls back to window, and updates the transform on each frame so it stays in sync with Lenis-driven smooth scrolling.