Product heroes often "animate" by slowly zooming a flat screenshot. That is Ken Burns, and it reads as filler. A real camera move changes the relationship between layers: push-in, pan, parallax, or a shallow orbit.
Switch the modes and replay.
Flat is scale alone — the wrong default. Push moves toward the subject (scale + implied Z). Pan slides the frame. Parallax lets near and far layers travel at different rates. Orbit tips the card in 3D so you feel a viewpoint, not a resize.
Pick one job
One camera move per beat. A hero that pans and orbits and parallaxes is noise. Use push when you want commitment (CTA, feature deep-dive). Use parallax when you have real layers. Use orbit sparingly for product chrome that benefits from a sense of volume.
Keep durations long enough to read (2–3s for a hero loop) and easings gentle. Camera moves are atmosphere, not feedback — they should never compete with a click.
Usage
import { motion } from "motion/react";
{/* Push-in */}
<motion.div
style={{ perspective: 900 }}
animate={{ scale: [0.94, 1.05], z: [0, 40] }}
transition={{ duration: 2.4, ease: [0.23, 1, 0.32, 1] }}
>
<Card />
</motion.div>
{/* Parallax — different x on each layer */}
<motion.div animate={{ x: [-12, 12] }} transition={{ duration: 2.6 }}>
<FarLayer />
</motion.div>
<motion.div animate={{ x: [-4, 4] }} transition={{ duration: 2.6 }}>
<NearLayer />
</motion.div>
.stage {
perspective: 900px;
}
@keyframes push {
from { transform: translateZ(0) scale(0.94); }
to { transform: translateZ(40px) scale(1.05); }
}
@keyframes orbit {
from { transform: rotateY(-16deg) rotateX(8deg); }
to { transform: rotateY(16deg) rotateX(4deg); }
}
.hero {
animation: push 2.4s cubic-bezier(0.23, 1, 0.32, 1) alternate infinite;
}
Prefer transform and perspective over animating width, top, or
background-size. Those are the difference between a camera and a stretch.