parallax /
Ken Burns Drift
Slow infinite zoom+pan loop. Pure CSS keyframes, no scroll, no JS. Gives a still photo a living-image feel.
Preview
Source
tsx
export interface ParallaxKenBurnsProps {
imageSrc?: string;
/** Loop duration in seconds. */
duration?: number;
eyebrow?: string;
headline?: string;
subhead?: string;
}
export default function ParallaxKenBurns({
imageSrc = "/heroes/blob-portrait/woman.webp",
duration = 22,
eyebrow = "Parallax · 07",
headline = "Ken Burns drift",
subhead =
"A slow, infinite zoom-and-pan loop. Not technically parallax — there's no scroll — but it gives a still photograph the same living-image feeling.",
}: ParallaxKenBurnsProps) {
return (
<section className="relative isolate flex min-h-[100svh] w-full items-center justify-center overflow-hidden bg-slate-900">
<style>{`
@keyframes innovationsKenBurns {
0% { transform: scale(1.0) translate3d(0%, 0%, 0); }
50% { transform: scale(1.18) translate3d(-3%, -2%, 0); }
100% { transform: scale(1.0) translate3d(0%, 0%, 0); }
}
.innovations-kenburns {
animation: innovationsKenBurns ${duration}s ease-in-out infinite;
will-change: transform;
transform-origin: center;
}
@media (prefers-reduced-motion: reduce) {
.innovations-kenburns { animation: none; }
}
`}</style>
<div
className="innovations-kenburns absolute inset-0 -z-10"
style={{
backgroundImage: `url(${imageSrc})`,
backgroundSize: "cover",
backgroundPosition: "center",
backgroundRepeat: "no-repeat",
}}
/>
<div className="absolute inset-0 -z-0 bg-gradient-to-b from-black/30 via-transparent to-black/70" />
<div className="relative z-10 mx-auto max-w-3xl px-6 text-center text-white">
<p className="mb-4 text-xs font-semibold uppercase tracking-[0.3em] text-white/70">
{eyebrow}
</p>
<h1 className="text-balance text-4xl font-semibold leading-[1.1] tracking-tight sm:text-6xl">
{headline}
</h1>
<p className="mx-auto mt-6 max-w-xl text-base leading-relaxed text-white/80 sm:text-lg">
{subhead}
</p>
</div>
</section>
);
} Claude Code Instructions
CLI Install
npx innovations add ken-burnsWhere to use it
Not technically parallax — but it solves the same problem (a still hero photo feels dead). The image slowly zooms and pans on a 22-second loop.
Pure CSS, zero JS. Honors prefers-reduced-motion (animation pauses).
WHEN TO USE:
- Top-of-page hero where the image needs to feel alive but you don't want scroll-coupled motion
- Background for testimonial sections, video lightboxes, modals
- Pages where you can't budget the JS for a real scroll listener
TUNING:
- duration prop (seconds) — slower = more cinematic. Default 22. Don't go below 12; it starts to feel like a fidget.
- Adjust scale/translate range inside the keyframes if 18% zoom is too aggressive for your image.