framer-motion-animator

Framer Motion Animator

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "framer-motion-animator" with this command: npx skills add patricio0312rev/skills/patricio0312rev-skills-framer-motion-animator

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 ( <motion.div initial={{ opacity: 0, y: 20 }} animate={{ opacity: 1, y: 0 }} transition={{ duration: 0.5 }} > {children} </motion.div> ); }

// Animate on hover export function ScaleOnHover({ children }: { children: React.ReactNode }) { return ( <motion.div whileHover={{ scale: 1.05 }} whileTap={{ scale: 0.95 }} transition={{ type: 'spring', stiffness: 400, damping: 17 }} > {children} </motion.div> ); }

Exit Animations with AnimatePresence

import { motion, AnimatePresence } from 'framer-motion';

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="fixed inset-0 bg-black/50 z-40" />

      {/* Modal */}
      &#x3C;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"
      >
        &#x3C;div className="bg-white rounded-xl p-6 max-w-md w-full">
          {children}
        &#x3C;/div>
      &#x3C;/motion.div>
    &#x3C;/>
  )}
&#x3C;/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 ( <motion.ul variants={containerVariants} initial="hidden" animate="visible" > {items.map((item, index) => ( <motion.li key={index} variants={itemVariants}> {item} </motion.li> ))} </motion.ul> ); }

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 ( <motion.button variants={buttonVariants} initial="initial" whileHover={disabled ? 'disabled' : 'hover'} whileTap={disabled ? 'disabled' : 'tap'} animate={disabled ? 'disabled' : 'initial'} onClick={onClick} disabled={disabled} className="px-4 py-2 bg-blue-500 text-white rounded-lg" > {children} </motion.button> ); }

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 ( <motion.div initial={{ opacity: 0, y: 20 }} animate={{ opacity: 1, y: 0 }} exit={{ opacity: 0, y: 20 }} transition={{ duration: 0.3 }} > {children} </motion.div> ); }

Shared Layout Animations

import { motion, LayoutGroup } from 'framer-motion';

export function Tabs({ tabs, activeTab, onTabChange }: TabsProps) { return ( <LayoutGroup> <div className="flex gap-2"> {tabs.map((tab) => ( <button key={tab.id} onClick={() => onTabChange(tab.id)} className="relative px-4 py-2" > {activeTab === tab.id && ( <motion.div layoutId="activeTab" className="absolute inset-0 bg-blue-500 rounded-lg" transition={{ type: 'spring', stiffness: 500, damping: 30 }} /> )} <span className="relative z-10">{tab.label}</span> </button> ))} </div> </LayoutGroup> ); }

Gesture Animations

Drag

export function DraggableCard() { return ( <motion.div drag dragConstraints={{ left: -100, right: 100, top: -100, bottom: 100 }} dragElastic={0.2} dragTransition={{ bounceStiffness: 600, bounceDamping: 20 }} whileDrag={{ scale: 1.1, cursor: 'grabbing' }} className="w-32 h-32 bg-blue-500 rounded-lg cursor-grab" /> ); }

Swipe to Dismiss

export function SwipeToDelete({ onDelete, children }: SwipeProps) { return ( <motion.div drag="x" dragConstraints={{ left: 0, right: 0 }} onDragEnd={(_, info) => { if (info.offset.x < -100) { onDelete(); } }} className="relative" > {children} <motion.div className="absolute right-0 inset-y-0 bg-red-500 flex items-center px-4" style={{ opacity: 0 }} animate={{ opacity: 1 }} > Delete </motion.div> </motion.div> ); }

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 ( <motion.div ref={ref} initial={{ opacity: 0, y: 50 }} animate={isInView ? { opacity: 1, y: 0 } : { opacity: 0, y: 50 }} transition={{ duration: 0.6, ease: 'easeOut' }} > {children} </motion.div> ); }

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 ( <motion.div style={{ y, opacity }} className="h-screen flex items-center justify-center" > <h1 className="text-6xl font-bold">Parallax Hero</h1> </motion.div> ); }

export function ScrollProgress() { const { scrollYProgress } = useScroll();

return ( <motion.div style={{ scaleX: scrollYProgress }} className="fixed top-0 left-0 right-0 h-1 bg-blue-500 origin-left z-50" /> ); }

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 ( <motion.button ref={scope} onClick={handleClick} className="px-4 py-2"> Submit </motion.button> ); }

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 ( <motion.div drag="x" dragConstraints={{ left: -200, right: 200 }} style={{ x, rotateY, opacity }} className="w-64 h-96 bg-gradient-to-br from-purple-500 to-pink-500 rounded-xl" /> ); }

Reusable Animation Components

AnimatedContainer

// components/AnimatedContainer.tsx import { motion, Variants } from 'framer-motion';

const animations: Record<string, Variants> = { 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 ( <motion.div variants={animations[animation]} initial="hidden" animate="visible" transition={{ duration, delay, ease: 'easeOut' }} className={className} > {children} </motion.div> ); }

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<T> { items: T[]; renderItem: (item: T, index: number) => React.ReactNode; keyExtractor: (item: T, index: number) => string; className?: string; }

export function AnimatedList<T>({ items, renderItem, keyExtractor, className, }: AnimatedListProps<T>) { return ( <motion.ul variants={containerVariants} initial="hidden" animate="visible" className={className} > {items.map((item, index) => ( <motion.li key={keyExtractor(item, index)} variants={itemVariants}> {renderItem(item, index)} </motion.li> ))} </motion.ul> ); }

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 <motion.div transition={transitions.spring} />

Reduced Motion Support

import { useReducedMotion } from 'framer-motion';

export function AccessibleAnimation({ children }: { children: React.ReactNode }) { const shouldReduceMotion = useReducedMotion();

return ( <motion.div initial={{ opacity: 0, y: shouldReduceMotion ? 0 : 20 }} animate={{ opacity: 1, y: 0 }} transition={{ duration: shouldReduceMotion ? 0 : 0.5 }} > {children} </motion.div> ); }

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

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

General

eslint-prettier-config

No summary provided by upstream source.

Repository SourceNeeds Review
General

postman-collection-generator

No summary provided by upstream source.

Repository SourceNeeds Review
General

changelog-writer

No summary provided by upstream source.

Repository SourceNeeds Review
General

nginx-config-optimizer

No summary provided by upstream source.

Repository SourceNeeds Review