
animation-designer
by deve1993
SKILL.md
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.
Score
Total Score
Based on repository quality metrics
SKILL.mdファイルが含まれている
ライセンスが設定されている
100文字以上の説明がある
GitHub Stars 100以上
3ヶ月以内に更新がある
10回以上フォークされている
オープンIssueが50未満
プログラミング言語が設定されている
1つ以上のタグが設定されている
Reviews
Reviews coming soon