Claude Code Plugins

Community-maintained marketplace

Feedback

animation-designer

@deve1993/Quickfy-website
1
0

Create advanced animations with Framer Motion, gesture handling, micro-interactions, spring physics, orchestration, and performance optimization for UI components

Install Skill

1Download skill
2Enable skills in Claude

Open claude.ai/settings/capabilities and find the "Skills" section

3Upload to Claude

Click "Upload skill" and select the downloaded ZIP file

Note: Please verify skill by going through its instructions before using it.

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

  1. Define Goals

    • What should animate?
    • Purpose of animation?
    • Interaction triggers?
    • Duration and timing?
  2. Choose Animation Type

    • Simple transitions?
    • Complex sequences?
    • Gesture-based?
    • Scroll-triggered?
  3. Plan Performance

    • Animation budget?
    • Mobile considerations?
    • Reduced motion support?

Phase 2: Implementation

  1. Set Up Framer Motion

    • Install dependencies
    • Create motion components
    • Define variants
  2. Implement Animations

    • Basic transitions
    • Gesture handlers
    • Spring configurations
    • Animation controls
  3. Add Orchestration

    • Stagger children
    • Sequence animations
    • Conditional logic

Phase 3: Polish & Optimize

  1. Fine-tune Timing

    • Adjust duration
    • Modify easing
    • Test spring values
  2. Optimize Performance

    • Use GPU properties only
    • Add will-change hints
    • Implement reduced motion
  3. 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

  1. Micro-interactions: 100-300ms (button hover, tap)
  2. Component transitions: 300-500ms (modal open, page transition)
  3. Complex sequences: 500-1000ms (multi-step animations)
  4. 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

  1. Respect prefers-reduced-motion: Always check and disable/simplify animations
  2. Keep focus visible: Don't animate elements that have focus
  3. Avoid flashing: No animations faster than 3 flashes per second
  4. Provide alternatives: Offer non-animated version if needed
  5. Test with screen readers: Ensure animations don't interfere

Performance

  1. 60 FPS Target: Keep animations smooth
  2. Use transform/opacity: GPU-accelerated properties only
  3. Avoid layout animations: Don't animate width/height/position
  4. Lazy load animations: Only animate when in viewport
  5. Batch animations: Group simultaneous animations
  6. Profile regularly: Use DevTools Performance tab

UX Guidelines

  1. Purpose-driven: Every animation should have a reason
  2. Subtle by default: Don't distract from content
  3. Consistent timing: Use animation system/tokens
  4. Interruptible: Allow users to skip/cancel animations
  5. 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:

  1. Complete Animation Component: Production-ready code with Framer Motion
  2. Variants Definition: Reusable animation states
  3. Spring Configurations: Fine-tuned physics parameters
  4. Performance Notes: GPU optimization and reduced motion support
  5. Accessibility Considerations: A11y compliance details
  6. Usage Examples: How to integrate animations

Always create animations that are smooth, purposeful, performant, and accessible.