Perspective Flow
Scroll-driven 3D image grid with perspective rotations, blur, brightness, and inner parallax — supports both GSAP ScrollTrigger and Framer Motion engines.
Installation
File Structure
Usage
import { PerspectiveFlow } from "@/components/unlumen-ui/perspective-flow";
const images = [
{ src: "/photos/1.jpg", alt: "Photo 1" },
{ src: "/photos/2.jpg", alt: "Photo 2" },
{ src: "/photos/3.jpg", alt: "Photo 3" },
{ src: "/photos/4.jpg", alt: "Photo 4" },
];
export default function Gallery() {
return <PerspectiveFlow images={images} />;
}With a custom scroll container
If your page uses an inner scroll area (overflow-y-auto), pass a scrollContainerRef so the animation listens to the correct scroller.
"use client";
import { useRef } from "react";
import { PerspectiveFlow } from "@/components/unlumen-ui/perspective-flow";
export default function ScrollAreaExample() {
const scrollRef = useRef<HTMLDivElement>(null);
return (
<div ref={scrollRef} className="h-[80vh] overflow-y-auto">
<PerspectiveFlow images={images} scrollContainerRef={scrollRef} />
</div>
);
}GSAP engine
Switch to GSAP for a stronger cinematic look:
<PerspectiveFlow images={images} engine="gsap" />Tuning density and spacing
<PerspectiveFlow
images={images}
perspective={900}
minItemWidth={200}
gap={24}
verticalGap={96}
/>gap and verticalGap accept both numbers (px) and CSS lengths ("1.5rem", "clamp(...)", etc.).
API Reference
imagesPerspectiveFlowImage[]—Array of image objects to display. Each item has `src` (string) and optional `alt` (string).
engine?"gsap" | "motion""motion"Animation engine. `"motion"` uses Framer Motion useScroll/useTransform. `"gsap"` uses GSAP ScrollTrigger timelines.
perspective?number1000CSS perspective depth in pixels applied to each card container.
maxWidth?string"900px"Max width of the grid container.
gap?number | string"1.5rem"Horizontal gap between items. Accepts `number` (px) or CSS string.
verticalGap?number | string—Vertical gap between rows. Defaults to `gap` when omitted. Accepts `number` (px) or CSS string.
minItemWidth?number240Minimum item width used by responsive auto-fit columns.
scrollContainerRef?RefObject<HTMLElement | null>—Optional ref to a custom scroll container. Required when not using viewport scroll.
className?string—Additional classes applied to the root element.
How it works
Each card is rendered as a figure with its own CSS perspective context. Both engines run a 3-phase scroll animation:
- Entering (bottom) — tilted back (
rotateX: 70°), shifted sideways, skewed, blurred (7px), dark (brightness: 0%), high contrast, and the inner image is vertically stretched (scaleY: 1.8) for a parallax effect. - Center (revealed) — all transforms reset to identity. No blur, full brightness, normal scale.
- Exiting (top) — tilts forward (
rotateX: -50°), slight counter-rotation, blur returns, goes dark again, inner image re-stretches.
Left/right cards are mirrored, and GSAP computes the left/right side relative to the active scroller center (not always the window), which keeps behavior consistent in preview containers.
Perspective-adaptive behavior
The component now auto-adapts motion and spacing based on perspective:
- Lower perspective: reduced transform amplitudes + increased effective spacing to avoid overlap.
- Higher perspective: stronger depth and larger motion range.
This makes low values like 200-400 still usable without collapsing cards into each other.
Notes
- The grid is intentionally capped to at most 2 columns for this component's visual language.
- The component is
"use client"because both engines require browser APIs (scroll position, DOM measurements). - Images are rendered as CSS
background-imagewithbg-cover bg-center— they fill the card and crop to center. will-change: filterandwill-change: transformare set for GPU compositing.- GSAP cleanup is scoped with
gsap.context(...).revert()to avoid touching unrelated ScrollTriggers.
Credits
Built by leo.
Inspired by and adapted from Skiper UI.
Keep in mind
Most components on this site are inspired by or recreated from existing work across the web. I'm not here to take credit; just to learn, experiment, and sometimes push things a bit further. If something looks familiar and I forgot to mention you, reach out and I'll fix that right away.
This component is part of Unlumen UI [ Pro ]
Includes advanced motion, patterns, and premium support.