Introduction
The Scroll velocity: 3D planes example lays a collection of photos out as a line of angled planes that rises into the distance. Scroll or drag it, and a wave ripples through the planes with a strength that follows your speed. Hover a plane and it lifts towards you with a scrambled label. Click it and it opens into a detail view, with the rest of the collection lined up in a strip below.
The Motion+ Gallery component does the heavy lifting. Its list arrangement places the planes, the ripple modifier makes the wave, and galleryDetail opens the detail view. We also use AnimatePresence, useTransform and the Motion+ ScrambleText component for the labels.
Get started
Let's start with the page: a header, a hint and a list of plane components, each with an image and an index.
"use client"
const images = Array.from({ length: 16 }, (_, i) => `/photos/${i + 1}.jpg`)
const labels = ["Afterglow", "Drift Frame", "Peripheral" /* ... */]
function Plane({ index }) {
return (
<div className="plane">
<img
src={images[index]}
alt={labels[index]}
className="plane-image"
draggable={false}
/>
<div className="plane-index">{String(index).padStart(2, "0")}</div>
</div>
)
}
export default function ScrollVelocityLinkedOffset() {
return (
<>
<div className="heritage">
<div className="header">
<div className="title">HERITAGE FW25/26</div>
<div className="title title-collection">COLLECTION</div>
</div>
<div className="hint">scroll to surf, click to open</div>
<div className="viewport">
{images.map((_, index) => (
<Plane key={index} index={index} />
))}
</div>
</div>
<Stylesheet />
</>
)
}
function Stylesheet() {
return (
<style>
{/** Copy styles from example source code */}
</style>
)
}
Right now the planes sit in a plain row. In the next steps, Gallery turns them into a 3D line that we can scroll, drag and open.







