Framer Motion Animator
Build delightful animations and interactions with Framer Motion's declarative API.
Core Workflow Identify animation needs: Entrance, exit, hover, gestures Choose animation type: Simple, variants, gestures, layout Define motion values: Opacity, scale, position, rotation Add transitions: Duration, easing, spring physics Orchestrate sequences: Stagger, delay, parent-child Optimize performance: GPU-accelerated properties Installation npm install framer-motion
Basic Animations Simple Animation import { motion } from 'framer-motion';
// Animate on mount
export function FadeIn({ children }: { children: React.ReactNode }) {
return (
// Animate on hover
export function ScaleOnHover({ children }: { children: React.ReactNode }) {
return (
Exit Animations with AnimatePresence import { motion, AnimatePresence } from 'framer-motion';
export function Modal({ isOpen, onClose, children }: ModalProps) {
return (
{/* Modal */}
<motion.div
initial={{ opacity: 0, scale: 0.95, y: 20 }}
animate={{ opacity: 1, scale: 1, y: 0 }}
exit={{ opacity: 0, scale: 0.95, y: 20 }}
transition={{ type: 'spring', damping: 25, stiffness: 300 }}
className="fixed inset-0 z-50 flex items-center justify-center"
>
<div className="bg-white rounded-xl p-6 max-w-md w-full">
{children}
</div>
</motion.div>
</>
)}
</AnimatePresence>
); }
Variants Pattern Staggered Children 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 (
Interactive Variants 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 (
Page Transitions Next.js App Router // app/template.tsx 'use client';
import { motion } from 'framer-motion';
export default function Template({ children }: { children: React.ReactNode }) {
return (
Shared Layout Animations import { motion, LayoutGroup } from 'framer-motion';
export function Tabs({ tabs, activeTab, onTabChange }: TabsProps) {
return (
Gesture Animations
Drag
export function DraggableCard() {
return (
Swipe to Dismiss
export function SwipeToDelete({ onDelete, children }: SwipeProps) {
return (
Scroll Animations Scroll-Triggered 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 (
Scroll Progress 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) 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 (
useMotionValue & useTransform 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 // components/AnimatedContainer.tsx import { motion, Variants } from 'framer-motion';
const animations: Record
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 (
AnimatedList // 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
export function AnimatedList
Transition Presets // 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 import { useReducedMotion } from 'framer-motion';
export function AccessibleAnimation({ children }: { children: React.ReactNode }) { const shouldReduceMotion = useReducedMotion();
return (
Best Practices Use GPU-accelerated properties: opacity, transform (not width, height) Add layout for smooth resizing: Automatic layout animations Use AnimatePresence: For exit animations Prefer springs: More natural than tween for UI Respect reduced motion: Use useReducedMotion hook Avoid animating layout thrashing: Don't animate top, left, width Use layoutId: For shared element transitions 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