Tilt Scroll Effect
Original · freeImage tilts subtly based on scroll position.
Copy prompt gives Claude Code, Cursor or v0 a ready-to-paste prompt that recreates this exact component.
npx shadcn@latest add https://webinnoventix.com/r/imgfx-tilt-scroll.json"use client";
import { useRef } from "react";
import {
motion,
useScroll,
useTransform,
useSpring,
useReducedMotion,
} from "motion/react";
export default function ImgfxTiltScroll() {
const reduce = useReducedMotion();
const sectionRef = useRef<HTMLDivElement>(null);
const { scrollYProgress } = useScroll({
target: sectionRef,
offset: ["start end", "end start"],
});
const spring = useSpring(scrollYProgress, {
stiffness: 120,
damping: 24,
mass: 0.4,
});
// As the image travels through the viewport it rocks from tilted-up,
// through flat at center, to tilted-down — a subtle parallax lean.
const rotateX = useTransform(spring, [0, 0.5, 1], [10, 0, -10]);
const rotateY = useTransform(spring, [0, 0.5, 1], [-9, 0, 9]);
const rotateZ = useTransform(spring, [0, 0.5, 1], [-2.2, 0, 2.2]);
const translateY = useTransform(spring, [0, 0.5, 1], [26, 0, -26]);
const glareX = useTransform(spring, [0, 1], [18, 82]);
const glareOpacity = useTransform(spring, [0, 0.5, 1], [0.05, 0.34, 0.05]);
const glareBackground = useTransform(
glareX,
(x) =>
`radial-gradient(60% 80% at ${x}% 12%, rgba(255,255,255,0.9), transparent 70%)`
);
const flat = { rotateX: 0, rotateY: 0, rotateZ: 0, y: 0 } as const;
return (
<section
ref={sectionRef}
className="relative w-full overflow-hidden bg-neutral-50 px-6 py-24 sm:py-32 dark:bg-neutral-950"
>
<style>{`
@keyframes tiltscroll_float {
0%, 100% { transform: translateY(0); }
50% { transform: translateY(-6px); }
}
.tiltscroll-hint { animation: tiltscroll_float 3.2s ease-in-out infinite; }
@media (prefers-reduced-motion: reduce) {
.tiltscroll-hint { animation: none !important; }
}
`}</style>
{/* soft ambient glows */}
<div
aria-hidden
className="pointer-events-none absolute -top-24 left-1/2 h-72 w-72 -translate-x-1/2 rounded-full bg-indigo-300/30 blur-3xl dark:bg-indigo-600/20"
/>
<div
aria-hidden
className="pointer-events-none absolute -bottom-32 right-0 h-80 w-80 rounded-full bg-violet-300/25 blur-3xl dark:bg-violet-700/15"
/>
<div className="relative mx-auto flex max-w-2xl flex-col items-center text-center">
<span className="inline-flex items-center gap-2 rounded-full border border-indigo-200 bg-white/70 px-3 py-1 text-[0.7rem] font-semibold uppercase tracking-[0.18em] text-indigo-600 backdrop-blur dark:border-indigo-500/30 dark:bg-white/5 dark:text-indigo-300">
<span className="h-1.5 w-1.5 rounded-full bg-indigo-500" />
Scroll Tilt
</span>
<h2 className="mt-6 text-balance text-3xl font-bold tracking-tight text-neutral-900 sm:text-4xl dark:text-neutral-50">
The frame leans as you move
</h2>
<p className="mt-4 max-w-md text-pretty text-base leading-relaxed text-neutral-600 dark:text-neutral-400">
Scroll the page and watch the photo rock on its axis — a quiet
parallax lean that flattens out right as it crosses the center.
</p>
</div>
<div
className="relative mx-auto mt-16 max-w-3xl"
style={{ perspective: "1200px" }}
>
<motion.div
className="group relative rounded-[1.75rem] will-change-transform"
style={
reduce
? undefined
: {
rotateX,
rotateY,
rotateZ,
y: translateY,
transformStyle: "preserve-3d",
}
}
initial={reduce ? undefined : flat}
>
{/* drop shadow plate behind the image, tilts with it */}
<div
aria-hidden
className="absolute inset-x-6 bottom-3 top-8 -z-10 rounded-[1.5rem] bg-neutral-900/25 blur-2xl dark:bg-black/60"
/>
<div className="relative overflow-hidden rounded-[1.75rem] border border-white/60 bg-white shadow-2xl ring-1 ring-black/5 dark:border-white/10 dark:bg-neutral-900 dark:ring-white/10">
{/* eslint-disable-next-line @next/next/no-img-element */}
<img
src="/img/gallery/g14.webp"
alt="Photographer leaning over a railing at golden hour, city skyline behind"
loading="lazy"
draggable={false}
className="aspect-[4/3] w-full select-none object-cover"
/>
{/* moving glare that tracks scroll */}
<motion.div
aria-hidden
className="pointer-events-none absolute inset-0 mix-blend-soft-light"
style={
reduce
? { opacity: 0 }
: {
opacity: glareOpacity,
background: glareBackground,
}
}
/>
{/* thin inner edge highlight */}
<div
aria-hidden
className="pointer-events-none absolute inset-0 rounded-[1.75rem] ring-1 ring-inset ring-white/25 dark:ring-white/5"
/>
</div>
{/* floating caption chip, sits slightly forward in 3D */}
<div
className="absolute -bottom-4 left-6 rounded-full border border-neutral-200 bg-white/90 px-4 py-2 text-xs font-medium text-neutral-700 shadow-lg backdrop-blur dark:border-white/10 dark:bg-neutral-900/90 dark:text-neutral-300"
style={reduce ? undefined : { transform: "translateZ(48px)" }}
>
Golden hour, handheld
</div>
</motion.div>
</div>
<p className="tiltscroll-hint mx-auto mt-16 flex items-center justify-center gap-2 text-sm text-neutral-500 dark:text-neutral-500">
<svg
width="15"
height="15"
viewBox="0 0 24 24"
fill="none"
aria-hidden
className="text-indigo-500"
>
<path
d="M12 5v14m0 0l-5-5m5 5l5-5"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
Keep scrolling to feel the lean
</p>
</section>
);
}Dependencies
Licence
Built by Web Innoventix. Free for personal and commercial use, no attribution required.
Built by Web Innoventix
Need a custom interface, a full website, or SEO that gets you cited by AI? We design, build and rank it end to end.
Get a free quoteSimilar components
Browse all →
Hover Zoom Effect
OriginalImage zooms in smoothly inside a fixed rounded frame on hover.

Hover Zoom Out Effect
OriginalImage starts zoomed and settles to normal on hover.

Zoom Rotate Effect
OriginalImage zooms and rotates slightly on hover.

Grayscale Color Effect
OriginalGreyscale image turns to full colour on hover.

Color Grayscale Effect
OriginalFull-colour image fades to greyscale on hover.

Blur Sharp Effect
OriginalBlurred image sharpens on hover.

Sharp Blur Effect
OriginalSharp image blurs with a caption appearing on hover.

Sepia Clear Effect
OriginalSepia-toned image clears to full colour on hover.

Saturate Pop Effect
OriginalMuted image saturates and pops on hover.

Contrast Pop Effect
OriginalLow-contrast image gains punchy contrast on hover.

Brightness Lift Effect
OriginalDim image brightens on hover.

Hue Rotate Effect
OriginalImage hue-shifts through colours on hover.

