--- name: framer-motion-animator description: Creates smooth animations and micro-interactions using Framer Motion including page transitions, gestures, scroll-based animations, and orchestrated sequences. Use when users request "add animation", "framer motion", "page transition", "animate component", or "micro-interactions". --- # Framer Motion Animator Build delightful animations and interactions with Framer Motion's declarative API. ## Core Workflow 1. **Identify animation needs**: Entrance, exit, hover, gestures 2. **Choose animation type**: Simple, variants, gestures, layout 3. **Define motion values**: Opacity, scale, position, rotation 4. **Add transitions**: Duration, easing, spring physics 5. **Orchestrate sequences**: Stagger, delay, parent-child 6. **Optimize performance**: GPU-accelerated properties ## Installation ```bash npm install framer-motion ``` ## Basic Animations ### Simple Animation ```tsx import { motion } from 'framer-motion'; // Animate on mount export function FadeIn({ children }: { children: React.ReactNode }) { return ( {children} ); } // Animate on hover export function ScaleOnHover({ children }: { children: React.ReactNode }) { return ( {children} ); } ``` ### Exit Animations with AnimatePresence ```tsx import { motion, AnimatePresence } from 'framer-motion'; export function Modal({ isOpen, onClose, children }: ModalProps) { return ( {isOpen && ( <> {/* Backdrop */} {/* Modal */} {children} > )} ); } ``` ## Variants Pattern ### Staggered Children ```tsx const containerVariants = { hidden: { opacity: 0 }, visible: { opacity: 1, transition: { staggerChildren: 0.1, delayChildren: 0.2, }, }, }; const itemVariants = { hidden: { opacity: 0, y: 20 }, visible: { opacity: 1, y: 0, transition: { type: 'spring', stiffness: 300, damping: 24 }, }, }; export function StaggeredList({ items }: { items: string[] }) { return ( {items.map((item, index) => ( {item} ))} ); } ``` ### Interactive Variants ```tsx const buttonVariants = { initial: { scale: 1 }, hover: { scale: 1.05 }, tap: { scale: 0.95 }, disabled: { opacity: 0.5, scale: 1 }, }; export function AnimatedButton({ children, disabled, onClick, }: ButtonProps) { return ( {children} ); } ``` ## Page Transitions ### Next.js App Router ```tsx // app/template.tsx 'use client'; import { motion } from 'framer-motion'; export default function Template({ children }: { children: React.ReactNode }) { return ( {children} ); } ``` ### Shared Layout Animations ```tsx import { motion, LayoutGroup } from 'framer-motion'; export function Tabs({ tabs, activeTab, onTabChange }: TabsProps) { return ( {tabs.map((tab) => ( onTabChange(tab.id)} className="relative px-4 py-2" > {activeTab === tab.id && ( )} {tab.label} ))} ); } ``` ## Gesture Animations ### Drag ```tsx export function DraggableCard() { return ( ); } ``` ### Swipe to Dismiss ```tsx export function SwipeToDelete({ onDelete, children }: SwipeProps) { return ( { if (info.offset.x < -100) { onDelete(); } }} className="relative" > {children} Delete ); } ``` ## Scroll Animations ### Scroll-Triggered ```tsx import { motion, useInView } from 'framer-motion'; import { useRef } from 'react'; export function FadeInWhenVisible({ children }: { children: React.ReactNode }) { const ref = useRef(null); const isInView = useInView(ref, { once: true, margin: '-100px' }); return ( {children} ); } ``` ### Scroll Progress ```tsx import { motion, useScroll, useTransform } from 'framer-motion'; export function ParallaxHero() { const { scrollY } = useScroll(); const y = useTransform(scrollY, [0, 500], [0, 150]); const opacity = useTransform(scrollY, [0, 300], [1, 0]); return ( Parallax Hero ); } export function ScrollProgress() { const { scrollYProgress } = useScroll(); return ( ); } ``` ## Animation Hooks ### useAnimate (Imperative) ```tsx import { useAnimate } from 'framer-motion'; export function SubmitButton() { const [scope, animate] = useAnimate(); const handleClick = async () => { // Sequence of animations await animate(scope.current, { scale: 0.95 }, { duration: 0.1 }); await animate(scope.current, { scale: 1 }, { type: 'spring' }); // Success animation await animate( scope.current, { backgroundColor: '#22c55e' }, { duration: 0.2 } ); }; return ( Submit ); } ``` ### useMotionValue & useTransform ```tsx import { motion, useMotionValue, useTransform } from 'framer-motion'; export function RotatingCard() { const x = useMotionValue(0); const rotateY = useTransform(x, [-200, 200], [-45, 45]); const opacity = useTransform(x, [-200, 0, 200], [0.5, 1, 0.5]); return ( ); } ``` ## Reusable Animation Components ### AnimatedContainer ```tsx // components/AnimatedContainer.tsx import { motion, Variants } from 'framer-motion'; const animations: Record = { fadeIn: { hidden: { opacity: 0 }, visible: { opacity: 1 }, }, fadeInUp: { hidden: { opacity: 0, y: 20 }, visible: { opacity: 1, y: 0 }, }, fadeInDown: { hidden: { opacity: 0, y: -20 }, visible: { opacity: 1, y: 0 }, }, scaleIn: { hidden: { opacity: 0, scale: 0.8 }, visible: { opacity: 1, scale: 1 }, }, slideInLeft: { hidden: { opacity: 0, x: -50 }, visible: { opacity: 1, x: 0 }, }, slideInRight: { hidden: { opacity: 0, x: 50 }, visible: { opacity: 1, x: 0 }, }, }; interface AnimatedContainerProps { children: React.ReactNode; animation?: keyof typeof animations; delay?: number; duration?: number; className?: string; } export function AnimatedContainer({ children, animation = 'fadeInUp', delay = 0, duration = 0.5, className, }: AnimatedContainerProps) { return ( {children} ); } ``` ### AnimatedList ```tsx // components/AnimatedList.tsx import { motion } from 'framer-motion'; const containerVariants = { hidden: { opacity: 0 }, visible: { opacity: 1, transition: { staggerChildren: 0.05, }, }, }; const itemVariants = { hidden: { opacity: 0, x: -20 }, visible: { opacity: 1, x: 0 }, }; interface AnimatedListProps { items: T[]; renderItem: (item: T, index: number) => React.ReactNode; keyExtractor: (item: T, index: number) => string; className?: string; } export function AnimatedList({ items, renderItem, keyExtractor, className, }: AnimatedListProps) { return ( {items.map((item, index) => ( {renderItem(item, index)} ))} ); } ``` ## Transition Presets ```tsx // lib/transitions.ts export const transitions = { spring: { type: 'spring', stiffness: 300, damping: 24, }, springBouncy: { type: 'spring', stiffness: 500, damping: 15, }, springStiff: { type: 'spring', stiffness: 700, damping: 30, }, smooth: { type: 'tween', duration: 0.3, ease: 'easeInOut', }, snappy: { type: 'tween', duration: 0.15, ease: [0.25, 0.1, 0.25, 1], }, } as const; // Usage ``` ## Reduced Motion Support ```tsx import { useReducedMotion } from 'framer-motion'; export function AccessibleAnimation({ children }: { children: React.ReactNode }) { const shouldReduceMotion = useReducedMotion(); return ( {children} ); } ``` ## Best Practices 1. **Use GPU-accelerated properties**: `opacity`, `transform` (not `width`, `height`) 2. **Add `layout` for smooth resizing**: Automatic layout animations 3. **Use `AnimatePresence`**: For exit animations 4. **Prefer springs**: More natural than tween for UI 5. **Respect reduced motion**: Use `useReducedMotion` hook 6. **Avoid animating layout thrashing**: Don't animate `top`, `left`, `width` 7. **Use `layoutId`**: For shared element transitions 8. **Stagger children**: For list animations ## Output Checklist Every animation implementation should include: - [ ] Appropriate animation type (simple, variants, gestures) - [ ] Smooth transitions with proper easing - [ ] Exit animations with AnimatePresence - [ ] Reduced motion support - [ ] GPU-accelerated properties only - [ ] Spring physics for natural feel - [ ] Staggered children for lists - [ ] Performance tested on low-end devices