description: Create polished, cinematic product demo animations using Framer Motion. Use this skill when building animated product showcases, feature reveal sequences, UI walkthroughs, marketing videos, or any React-based demo that needs professional motion design. Covers orchestration, scroll-triggered reveals, gesture animations, layout transitions, and cinematic timing patterns.
Create cinematic, professional product demo animations that showcase features with impact.
import { motion, AnimatePresence, useScroll, useTransform } from "motion/react"Use these easing curves for professional feel:
const easing = {
smooth: [0.4, 0, 0.2, 1], // General UI
snappy: [0.4, 0, 0, 1], // Quick interactions
dramatic: [0.16, 1, 0.3, 1], // Hero reveals
elastic: [0.68, -0.6, 0.32, 1.6] // Playful bounce
}const container = {
hidden: { opacity: 0 },
show: {
opacity: 1,
transition: { staggerChildren: 0.15, delayChildren: 0.3 }
}
}
const item = {
hidden: { opacity: 0, y: 40 },
show: { opacity: 1, y: 0, transition: { duration: 0.6, ease: easing.dramatic } }
}
<motion.div variants={container} initial="hidden" animate="show">
{features.map(f => <motion.div key={f.id} variants={item}>{f.content}</motion.div>)}
</motion.div><motion.div
initial={{ scale: 0.8, opacity: 0, y: 60 }}
animate={{ scale: 1, opacity: 1, y: 0 }}
transition={{ duration: 1.2, ease: easing.dramatic }}
/>function ScrollReveal({ children }) {
return (
<motion.div
initial={{ opacity: 0, y: 80 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-100px" }}
transition={{ duration: 0.8, ease: easing.smooth }}
>
{children}
</motion.div>
)
}For timed demo sequences (auto-playing walkthroughs):
function DemoSequence() {
const [step, setStep] = useState(0)
useEffect(() => {
const timings = [2000, 3000, 2500, 4000] // ms per step
const timer = setTimeout(() => {
setStep(s => (s + 1) % timings.length)
}, timings[step])
return () => clearTimeout(timer)
}, [step])
return (
<AnimatePresence mode="wait">
<motion.div
key={step}
initial={{ opacity: 0, x: 40 }}
animate={{ opacity: 1, x: 0 }}
exit={{ opacity: 0, x: -40 }}
transition={{ duration: 0.5 }}
>
{steps[step]}
</motion.div>
</AnimatePresence>
)
}Simulate clicks, hovers, typing for demos:
// Animated cursor
<motion.div
className="cursor"
animate={{ x: [0, 200, 200], y: [0, 0, 150] }}
transition={{ duration: 2, times: [0, 0.4, 1], ease: "easeInOut" }}
/>
// Simulated typing
function TypeWriter({ text, delay = 0 }) {
const [displayed, setDisplayed] = useState("")
useEffect(() => {
const timeout = setTimeout(() => {
let i = 0
const interval = setInterval(() => {
setDisplayed(text.slice(0, i + 1))
i++
if (i >= text.length) clearInterval(interval)
}, 50)
}, delay)
return () => clearTimeout(timeout)
}, [text, delay])
return <span>{displayed}<motion.span animate={{ opacity: [1, 0] }} transition={{ repeat: Infinity, duration: 0.8 }}>|</motion.span></span>
}Smooth morphing between states:
<motion.div layout layoutId="feature-card" transition={{ type: "spring", stiffness: 300, damping: 30 }}>
{expanded ? <ExpandedView /> : <CompactView />}
</motion.div>function ParallaxLayer({ speed = 0.5, children }) {
const { scrollY } = useScroll()
const y = useTransform(scrollY, [0, 1000], [0, 1000 * speed])
return <motion.div style={{ y }}>{children}</motion.div>
}Draw attention to features:
<motion.div
animate={{ scale: [1, 1.05, 1], boxShadow: ["0 0 0 0 rgba(59,130,246,0)", "0 0 0 8px rgba(59,130,246,0.3)", "0 0 0 0 rgba(59,130,246,0)"] }}
transition={{ duration: 2, repeat: Infinity }}
/>function BeforeAfter({ before, after }) {
const [split, setSplit] = useState(50)
return (
<div className="relative overflow-hidden" onMouseMove={e => setSplit((e.nativeEvent.offsetX / e.currentTarget.offsetWidth) * 100)}>
<div className="absolute inset-0">{before}</div>
<motion.div className="absolute inset-0" style={{ clipPath: `inset(0 0 0 ${split}%)` }}>{after}</motion.div>
</div>
)
}function Counter({ from = 0, to, duration = 2 }) {
const [value, setValue] = useState(from)
useEffect(() => {
const start = Date.now()
const tick = () => {
const progress = Math.min((Date.now() - start) / (duration * 1000), 1)
setValue(Math.floor(from + (to - from) * progress))
if (progress < 1) requestAnimationFrame(tick)
}
tick()
}, [from, to, duration])
return <motion.span>{value.toLocaleString()}</motion.span>
}- Use
will-change: transformsparingly on animated elements - Prefer
transformandopacityover layout-triggering properties - Use
layoutIdfor shared element transitions instead of manual calculations - Set
viewport={{ once: true }}for scroll animations that don’t need to repeat - Use
AnimatePresencewithmode="wait"for clean exit/enter sequences
export default function ProductDemo() {
return (
<>
{/* Hero with dramatic entrance */}
<section><HeroReveal /></section>
{/* Scroll-triggered feature sections */}
{features.map(f => (
<ScrollReveal key={f.id}><FeatureSection {...f} /></ScrollReveal>
))}
{/* Interactive demo sequence */}
<section><DemoSequence steps={demoSteps} /></section>
{/* Stats with counters */}
<section><StatsSection /></section>
{/* Final CTA with attention animation */}
<section><CTAWithPulse /></section>
</>
)
}| Effect | Key Props |
|---|---|
| Fade in | initial={{ opacity: 0 }} animate={{ opacity: 1 }} |
| Slide up | initial={{ y: 40 }} animate={{ y: 0 }} |
| Scale | initial={{ scale: 0.9 }} animate={{ scale: 1 }} |
| Stagger | Parent: staggerChildren: 0.1 |
| On scroll | whileInView={{ opacity: 1 }} viewport={{ once: true }} |
| On hover | whileHover={{ scale: 1.05 }} |
| Spring | transition={{ type: "spring", stiffness: 300 }} |
| Sequence | transition={{ delay: 0.5 }} or keyframe arrays |