| name | animation-designer |
| description | Create advanced animations with Framer Motion, gesture handling, micro-interactions, spring physics, orchestration, and performance optimization for UI components |
| allowed-tools | Read, Write, Edit, Bash, Glob, Grep, Task |
Animation Designer
Expert skill for creating production-ready animations for UI components. Specializes in Framer Motion, gesture interactions, spring physics, animation orchestration, scroll-triggered animations, and performance optimization.
Core Capabilities
1. Animation Types
- Entrance Animations: Fade, slide, scale, rotate on mount
- Exit Animations: Smooth transitions on unmount
- Hover Effects: Interactive micro-interactions
- Gesture Animations: Drag, tap, pan gestures
- Scroll Animations: Parallax, scroll-triggered effects
- Layout Animations: Automatic layout transitions
- Keyframe Animations: Complex multi-step animations
- SVG Animations: Path drawing, morphing
2. Framer Motion Integration
- Motion Components: Animated variants of HTML elements
- Variants: Reusable animation states
- Animation Controls: Programmatic animation control
- Shared Layout: Smooth transitions between components
- AnimatePresence: Exit animations for removed elements
- useMotionValue: Low-level animation control
- useSpring: Spring physics animations
3. Gesture Handling
- Drag: Draggable elements with constraints
- Hover: Hover effects with scale/color changes
- Tap: Press animations and haptic feedback
- Pan: Swipe gestures with momentum
- Focus: Keyboard focus animations
- Constraints: Limit drag areas
- Momentum: Inertia-based animations
4. Spring Physics
- Natural Motion: Physics-based springs
- Stiffness: Control spring tension
- Damping: Control oscillation
- Mass: Control inertia
- Presets: Quick spring configurations
- Velocity: Initial velocity control
5. Animation Orchestration
- Stagger Children: Cascade animations
- Sequence: Chain animations
- Delay: Time-based delays
- When: Conditional orchestration
- Repeat: Loop animations
- YoYo: Reverse animations
6. Performance
- GPU Acceleration: Use transform/opacity
- Will-change: Hint browser for optimization
- Reduce Motion: Respect user preferences
- Lazy Animation: Animate only in viewport
- Layout Thrashing: Avoid forced reflows
- Animation Budget: 60fps target
Workflow
Phase 1: Animation Planning
Define Goals
- What should animate?
- Purpose of animation?
- Interaction triggers?
- Duration and timing?
Choose Animation Type
- Simple transitions?
- Complex sequences?
- Gesture-based?
- Scroll-triggered?
Plan Performance
- Animation budget?
- Mobile considerations?
- Reduced motion support?
Phase 2: Implementation
Set Up Framer Motion
- Install dependencies
- Create motion components
- Define variants
Implement Animations
- Basic transitions
- Gesture handlers
- Spring configurations
- Animation controls
Add Orchestration
- Stagger children
- Sequence animations
- Conditional logic
Phase 3: Polish & Optimize
Fine-tune Timing
- Adjust duration
- Modify easing
- Test spring values
Optimize Performance
- Use GPU properties only
- Add will-change hints
- Implement reduced motion
Test Interactions
- Test all gestures
- Verify animations
- Check accessibility
Animation Patterns
Basic Entrance Animation
// FadeIn.tsx
import { motion } from 'framer-motion'
export function FadeIn({ children }: { children: React.ReactNode }) {
return (
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: -20 }}
transition={{ duration: 0.3 }}
>
{children}
</motion.div>
)
}
Variants Pattern (Reusable States)
// AnimatedCard.tsx
import { motion } from 'framer-motion'
const cardVariants = {
hidden: {
opacity: 0,
scale: 0.8,
y: 50,
},
visible: {
opacity: 1,
scale: 1,
y: 0,
transition: {
duration: 0.5,
ease: 'easeOut',
},
},
hover: {
scale: 1.05,
boxShadow: '0 10px 30px rgba(0,0,0,0.2)',
transition: {
duration: 0.2,
},
},
tap: {
scale: 0.95,
},
}
export function AnimatedCard({ children }: { children: React.ReactNode }) {
return (
<motion.div
variants={cardVariants}
initial="hidden"
animate="visible"
whileHover="hover"
whileTap="tap"
className="card"
>
{children}
</motion.div>
)
}
Stagger Children Animation
// StaggerList.tsx
import { motion } from 'framer-motion'
const containerVariants = {
hidden: { opacity: 0 },
visible: {
opacity: 1,
transition: {
staggerChildren: 0.1,
delayChildren: 0.2,
},
},
}
const itemVariants = {
hidden: { opacity: 0, x: -20 },
visible: {
opacity: 1,
x: 0,
transition: {
duration: 0.4,
},
},
}
interface StaggerListProps {
items: string[]
}
export function StaggerList({ items }: StaggerListProps) {
return (
<motion.ul variants={containerVariants} initial="hidden" animate="visible">
{items.map((item, index) => (
<motion.li key={index} variants={itemVariants}>
{item}
</motion.li>
))}
</motion.ul>
)
}
Draggable Component
// DraggableBox.tsx
import { motion, useMotionValue, useTransform } from 'framer-motion'
export function DraggableBox() {
const x = useMotionValue(0)
const y = useMotionValue(0)
// Rotate based on drag position
const rotateX = useTransform(y, [-100, 100], [30, -30])
const rotateY = useTransform(x, [-100, 100], [-30, 30])
return (
<motion.div
drag
dragConstraints={{ left: -100, right: 100, top: -100, bottom: 100 }}
dragElastic={0.1}
dragTransition={{ bounceStiffness: 600, bounceDamping: 20 }}
whileDrag={{ scale: 1.1, cursor: 'grabbing' }}
style={{
x,
y,
rotateX,
rotateY,
cursor: 'grab',
}}
className="draggable-box"
>
Drag me!
</motion.div>
)
}
Spring Animation
// BouncyButton.tsx
import { motion } from 'framer-motion'
const springConfig = {
type: 'spring' as const,
stiffness: 300,
damping: 20,
mass: 1,
}
interface BouncyButtonProps {
children: React.ReactNode
onClick?: () => void
}
export function BouncyButton({ children, onClick }: BouncyButtonProps) {
return (
<motion.button
whileHover={{ scale: 1.1 }}
whileTap={{ scale: 0.9 }}
transition={springConfig}
onClick={onClick}
className="bouncy-button"
>
{children}
</motion.button>
)
}
Scroll-Triggered Animation
// ScrollFadeIn.tsx
import { motion, useScroll, useTransform } from 'framer-motion'
import { useRef } from 'react'
export function ScrollFadeIn({ children }: { children: React.ReactNode }) {
const ref = useRef<HTMLDivElement>(null)
const { scrollYProgress } = useScroll({
target: ref,
offset: ['start end', 'end start'],
})
const opacity = useTransform(scrollYProgress, [0, 0.5, 1], [0, 1, 0])
const scale = useTransform(scrollYProgress, [0, 0.5, 1], [0.8, 1, 0.8])
return (
<motion.div ref={ref} style={{ opacity, scale }}>
{children}
</motion.div>
)
}
AnimatePresence for Exit Animations
// Modal.tsx
import { motion, AnimatePresence } from 'framer-motion'
interface ModalProps {
isOpen: boolean
onClose: () => void
children: React.ReactNode
}
export function Modal({ isOpen, onClose, children }: ModalProps) {
return (
<AnimatePresence>
{isOpen && (
<>
{/* Backdrop */}
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
onClick={onClose}
className="modal-backdrop"
/>
{/* Modal Content */}
<motion.div
initial={{ opacity: 0, scale: 0.8, y: 50 }}
animate={{ opacity: 1, scale: 1, y: 0 }}
exit={{ opacity: 0, scale: 0.8, y: 50 }}
transition={{ type: 'spring', damping: 25, stiffness: 300 }}
className="modal-content"
>
{children}
</motion.div>
</>
)}
</AnimatePresence>
)
}
Shared Layout Animation
// TabNav.tsx
import { motion } from 'framer-motion'
import { useState } from 'react'
const tabs = ['Home', 'About', 'Contact', 'Blog']
export function TabNav() {
const [activeTab, setActiveTab] = useState(0)
return (
<div className="tab-nav">
{tabs.map((tab, index) => (
<button
key={tab}
onClick={() => setActiveTab(index)}
className={activeTab === index ? 'active' : ''}
style={{ position: 'relative' }}
>
{tab}
{activeTab === index && (
<motion.div
layoutId="active-tab-indicator"
className="tab-indicator"
initial={false}
transition={{ type: 'spring', stiffness: 500, damping: 30 }}
/>
)}
</button>
))}
</div>
)
}
Path Drawing Animation
// AnimatedLogo.tsx
import { motion } from 'framer-motion'
const pathVariants = {
hidden: {
pathLength: 0,
opacity: 0,
},
visible: {
pathLength: 1,
opacity: 1,
transition: {
duration: 2,
ease: 'easeInOut',
},
},
}
export function AnimatedLogo() {
return (
<svg width="200" height="200" viewBox="0 0 200 200">
<motion.path
d="M 50 100 L 150 100 M 100 50 L 100 150"
stroke="currentColor"
strokeWidth="4"
fill="none"
variants={pathVariants}
initial="hidden"
animate="visible"
/>
</svg>
)
}
Programmatic Animation Control
// ControlledAnimation.tsx
import { motion, useAnimation } from 'framer-motion'
import { useEffect } from 'react'
export function ControlledAnimation() {
const controls = useAnimation()
useEffect(() => {
const sequence = async () => {
await controls.start({ scale: 1.5, transition: { duration: 0.5 } })
await controls.start({ rotate: 180, transition: { duration: 0.5 } })
await controls.start({ scale: 1, rotate: 0, transition: { duration: 0.5 } })
}
sequence()
}, [controls])
return (
<motion.div animate={controls} className="controlled-box">
Controlled Animation
</motion.div>
)
}
Infinite Loop Animation
// PulsingCircle.tsx
import { motion } from 'framer-motion'
export function PulsingCircle() {
return (
<motion.div
animate={{
scale: [1, 1.2, 1],
opacity: [0.5, 1, 0.5],
}}
transition={{
duration: 2,
repeat: Infinity,
ease: 'easeInOut',
}}
className="pulsing-circle"
/>
)
}
Advanced Patterns
Gesture-Based Card Stack
// CardStack.tsx
import { motion, useMotionValue, useTransform, PanInfo } from 'framer-motion'
import { useState } from 'react'
interface Card {
id: number
content: string
}
export function CardStack({ cards }: { cards: Card[] }) {
const [currentIndex, setCurrentIndex] = useState(0)
const x = useMotionValue(0)
const rotate = useTransform(x, [-200, 200], [-45, 45])
const opacity = useTransform(x, [-200, -100, 0, 100, 200], [0, 1, 1, 1, 0])
const handleDragEnd = (event: MouseEvent | TouchEvent | PointerEvent, info: PanInfo) => {
const swipeThreshold = 100
if (Math.abs(info.offset.x) > swipeThreshold) {
// Swiped
setCurrentIndex((prev) => (prev + 1) % cards.length)
}
}
return (
<div className="card-stack">
<motion.div
drag="x"
dragConstraints={{ left: 0, right: 0 }}
style={{ x, rotate, opacity }}
onDragEnd={handleDragEnd}
className="card"
>
{cards[currentIndex].content}
</motion.div>
</div>
)
}
Parallax Scroll Effect
// ParallaxSection.tsx
import { motion, useScroll, useTransform } from 'framer-motion'
import { useRef } from 'react'
export function ParallaxSection({ children }: { children: React.ReactNode }) {
const ref = useRef<HTMLDivElement>(null)
const { scrollYProgress } = useScroll({
target: ref,
offset: ['start end', 'end start'],
})
const y = useTransform(scrollYProgress, [0, 1], ['-20%', '20%'])
const opacity = useTransform(scrollYProgress, [0, 0.2, 0.8, 1], [0, 1, 1, 0])
return (
<div ref={ref} style={{ overflow: 'hidden' }}>
<motion.div style={{ y, opacity }}>{children}</motion.div>
</div>
)
}
Morphing Shape
// MorphingShape.tsx
import { motion } from 'framer-motion'
import { useState } from 'react'
const shapes = {
circle: 'M 100 50 A 50 50 0 1 1 100 150 A 50 50 0 1 1 100 50',
square: 'M 50 50 L 150 50 L 150 150 L 50 150 Z',
triangle: 'M 100 50 L 150 150 L 50 150 Z',
}
export function MorphingShape() {
const [shape, setShape] = useState<keyof typeof shapes>('circle')
return (
<>
<svg width="200" height="200" viewBox="0 0 200 200">
<motion.path
d={shapes[shape]}
fill="currentColor"
initial={false}
animate={{ d: shapes[shape] }}
transition={{ duration: 0.5, ease: 'easeInOut' }}
/>
</svg>
<div className="shape-controls">
{Object.keys(shapes).map((s) => (
<button key={s} onClick={() => setShape(s as keyof typeof shapes)}>
{s}
</button>
))}
</div>
</>
)
}
Page Transition
// PageTransition.tsx
import { motion, AnimatePresence } from 'framer-motion'
import { useLocation } from 'react-router-dom'
const pageVariants = {
initial: {
opacity: 0,
x: -100,
},
enter: {
opacity: 1,
x: 0,
transition: {
duration: 0.3,
ease: 'easeOut',
},
},
exit: {
opacity: 0,
x: 100,
transition: {
duration: 0.3,
ease: 'easeIn',
},
},
}
export function PageTransition({ children }: { children: React.ReactNode }) {
const location = useLocation()
return (
<AnimatePresence mode="wait">
<motion.div
key={location.pathname}
variants={pageVariants}
initial="initial"
animate="enter"
exit="exit"
>
{children}
</motion.div>
</AnimatePresence>
)
}
Performance Optimization
GPU-Accelerated Properties
// Only animate these properties for best performance:
// - transform (scale, rotate, translateX, translateY)
// - opacity
// ✅ GOOD - GPU accelerated
<motion.div
animate={{
x: 100, // translateX
y: 100, // translateY
scale: 1.5,
rotate: 45,
opacity: 0.5,
}}
/>
// ❌ BAD - Causes layout thrashing
<motion.div
animate={{
width: '500px', // Triggers layout
height: '500px', // Triggers layout
top: '100px', // Triggers layout
left: '100px', // Triggers layout
}}
/>
Reduced Motion Support
// RespectReducedMotion.tsx
import { motion, useReducedMotion } from 'framer-motion'
export function RespectReducedMotion({ children }: { children: React.ReactNode }) {
const shouldReduceMotion = useReducedMotion()
return (
<motion.div
initial={{ opacity: 0, y: shouldReduceMotion ? 0 : 50 }}
animate={{ opacity: 1, y: 0 }}
transition={{
duration: shouldReduceMotion ? 0 : 0.5,
}}
>
{children}
</motion.div>
)
}
Lazy Animation (Viewport Detection)
// LazyAnimation.tsx
import { motion, useInView } from 'framer-motion'
import { useRef } from 'react'
export function LazyAnimation({ children }: { children: React.ReactNode }) {
const ref = useRef<HTMLDivElement>(null)
const isInView = useInView(ref, { once: true, amount: 0.3 })
return (
<motion.div
ref={ref}
initial={{ opacity: 0, y: 50 }}
animate={isInView ? { opacity: 1, y: 0 } : { opacity: 0, y: 50 }}
transition={{ duration: 0.6 }}
>
{children}
</motion.div>
)
}
Will-Change Optimization
// OptimizedAnimation.tsx
import { motion } from 'framer-motion'
export function OptimizedAnimation({ children }: { children: React.ReactNode }) {
return (
<motion.div
style={{
// Hint browser to optimize these properties
willChange: 'transform, opacity',
}}
whileHover={{ scale: 1.1 }}
whileTap={{ scale: 0.95 }}
>
{children}
</motion.div>
)
}
Best Practices
Animation Timing
- Micro-interactions: 100-300ms (button hover, tap)
- Component transitions: 300-500ms (modal open, page transition)
- Complex sequences: 500-1000ms (multi-step animations)
- Ambient animations: 2-5s (infinite loops, breathing effects)
Easing Functions
- easeOut: Entrances (starts fast, ends slow)
- easeIn: Exits (starts slow, ends fast)
- easeInOut: Smooth both ways (default for most)
- linear: Constant speed (loading indicators)
- spring: Natural, physics-based (interactive elements)
Accessibility
- Respect prefers-reduced-motion: Always check and disable/simplify animations
- Keep focus visible: Don't animate elements that have focus
- Avoid flashing: No animations faster than 3 flashes per second
- Provide alternatives: Offer non-animated version if needed
- Test with screen readers: Ensure animations don't interfere
Performance
- 60 FPS Target: Keep animations smooth
- Use transform/opacity: GPU-accelerated properties only
- Avoid layout animations: Don't animate width/height/position
- Lazy load animations: Only animate when in viewport
- Batch animations: Group simultaneous animations
- Profile regularly: Use DevTools Performance tab
UX Guidelines
- Purpose-driven: Every animation should have a reason
- Subtle by default: Don't distract from content
- Consistent timing: Use animation system/tokens
- Interruptible: Allow users to skip/cancel animations
- Contextual: Match animation to the action
When to Use This Skill
Activate this skill when you need to:
- Add entrance/exit animations to components
- Create hover effects and micro-interactions
- Implement drag-and-drop functionality
- Build gesture-based interfaces
- Add scroll-triggered animations
- Create page transitions
- Animate SVG graphics
- Optimize animation performance
- Implement shared layout transitions
- Build complex animation sequences
Output Format
When creating animations, provide:
- Complete Animation Component: Production-ready code with Framer Motion
- Variants Definition: Reusable animation states
- Spring Configurations: Fine-tuned physics parameters
- Performance Notes: GPU optimization and reduced motion support
- Accessibility Considerations: A11y compliance details
- Usage Examples: How to integrate animations
Always create animations that are smooth, purposeful, performant, and accessible.