Back to list
mgd34msu

framer-motion

by mgd34msu

Plug in, Receive good vibes.

2🍴 0📅 Jan 25, 2026

SKILL.md


name: framer-motion description: Creates animations with Framer Motion/Motion including transitions, gestures, layout animations, and scroll effects. Use when animating React components, creating page transitions, adding gesture interactions, or building animated interfaces.

Framer Motion

Production-ready animation library for React with declarative animations and gestures.

Quick Start

Install:

npm install framer-motion

Basic animation:

import { motion } from 'framer-motion';

function Component() {
  return (
    <motion.div
      initial={{ opacity: 0 }}
      animate={{ opacity: 1 }}
      transition={{ duration: 0.5 }}
    >
      Hello World
    </motion.div>
  );
}

Core Concepts

Motion Components

import { motion } from 'framer-motion';

// Any HTML element
<motion.div />
<motion.span />
<motion.button />
<motion.svg />
<motion.path />

// Custom components
const MotionCard = motion(Card);

Animate Prop

// Simple values
<motion.div animate={{ x: 100 }} />

// Multiple properties
<motion.div
  animate={{
    x: 100,
    opacity: 0.5,
    scale: 1.2,
    rotate: 45,
  }}
/>

// Keyframes
<motion.div
  animate={{
    x: [0, 100, 0],
    opacity: [1, 0.5, 1],
  }}
/>

Initial State

<motion.div
  initial={{ opacity: 0, y: 20 }}
  animate={{ opacity: 1, y: 0 }}
/>

// Disable initial animation
<motion.div initial={false} animate={{ x: 100 }} />

Exit Animations

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

function Modal({ isOpen, onClose }) {
  return (
    <AnimatePresence>
      {isOpen && (
        <motion.div
          initial={{ opacity: 0 }}
          animate={{ opacity: 1 }}
          exit={{ opacity: 0 }}
          className="modal-backdrop"
          onClick={onClose}
        >
          <motion.div
            initial={{ scale: 0.9, opacity: 0 }}
            animate={{ scale: 1, opacity: 1 }}
            exit={{ scale: 0.9, opacity: 0 }}
            onClick={(e) => e.stopPropagation()}
            className="modal-content"
          >
            Modal content
          </motion.div>
        </motion.div>
      )}
    </AnimatePresence>
  );
}

Transitions

Basic Transitions

<motion.div
  animate={{ x: 100 }}
  transition={{
    duration: 0.5,
    delay: 0.2,
    ease: 'easeInOut',
  }}
/>

Spring Physics

<motion.div
  animate={{ x: 100 }}
  transition={{
    type: 'spring',
    stiffness: 100,
    damping: 10,
    mass: 1,
  }}
/>

// Bounce effect
<motion.div
  animate={{ y: 0 }}
  initial={{ y: -100 }}
  transition={{
    type: 'spring',
    bounce: 0.5,
  }}
/>

Tween (Keyframe)

<motion.div
  animate={{ x: 100 }}
  transition={{
    type: 'tween',
    ease: 'easeInOut',
    duration: 0.5,
  }}
/>

// Custom easing
<motion.div
  transition={{
    ease: [0.17, 0.67, 0.83, 0.67], // Cubic bezier
  }}
/>

Per-Property Transitions

<motion.div
  animate={{ x: 100, opacity: 0 }}
  transition={{
    x: { type: 'spring', stiffness: 100 },
    opacity: { duration: 0.2 },
  }}
/>

Variants

Basic Variants

const variants = {
  hidden: { opacity: 0, y: 20 },
  visible: { opacity: 1, y: 0 },
};

<motion.div
  variants={variants}
  initial="hidden"
  animate="visible"
/>

Orchestration

const container = {
  hidden: { opacity: 0 },
  visible: {
    opacity: 1,
    transition: {
      staggerChildren: 0.1,
      delayChildren: 0.3,
    },
  },
};

const item = {
  hidden: { opacity: 0, y: 20 },
  visible: { opacity: 1, y: 0 },
};

function List({ items }) {
  return (
    <motion.ul
      variants={container}
      initial="hidden"
      animate="visible"
    >
      {items.map((item) => (
        <motion.li key={item.id} variants={item}>
          {item.text}
        </motion.li>
      ))}
    </motion.ul>
  );
}

Dynamic Variants

const variants = {
  hidden: { opacity: 0 },
  visible: (custom: number) => ({
    opacity: 1,
    transition: { delay: custom * 0.1 },
  }),
};

{items.map((item, i) => (
  <motion.div
    key={item.id}
    custom={i}
    variants={variants}
    initial="hidden"
    animate="visible"
  />
))}

Gestures

Hover

<motion.button
  whileHover={{ scale: 1.05 }}
  whileTap={{ scale: 0.95 }}
>
  Click me
</motion.button>

// With variants
<motion.div
  whileHover="hover"
  variants={{
    hover: { scale: 1.1, rotate: 5 },
  }}
/>

Tap/Click

<motion.div
  whileTap={{ scale: 0.9 }}
  onTap={() => console.log('Tapped!')}
  onTapStart={() => console.log('Tap started')}
  onTapCancel={() => console.log('Tap cancelled')}
/>

Drag

<motion.div
  drag
  dragConstraints={{ left: 0, right: 300, top: 0, bottom: 300 }}
  dragElastic={0.2}
  dragMomentum={false}
  whileDrag={{ scale: 1.1 }}
  onDragStart={(event, info) => console.log('Drag started')}
  onDrag={(event, info) => console.log('Dragging', info.point)}
  onDragEnd={(event, info) => console.log('Drag ended')}
/>

// Constrain to parent
<motion.div ref={constraintsRef}>
  <motion.div drag dragConstraints={constraintsRef} />
</motion.div>

// Drag axis
<motion.div drag="x" /> // Horizontal only
<motion.div drag="y" /> // Vertical only

Focus

<motion.input
  whileFocus={{ scale: 1.02, borderColor: '#3b82f6' }}
/>

Layout Animations

Basic Layout

<motion.div layout>
  {isExpanded ? 'Expanded content' : 'Collapsed'}
</motion.div>

Shared Layout

function Tabs({ tabs, activeTab, setActiveTab }) {
  return (
    <div className="tabs">
      {tabs.map((tab) => (
        <button
          key={tab.id}
          onClick={() => setActiveTab(tab.id)}
          className="tab"
        >
          {tab.label}
          {activeTab === tab.id && (
            <motion.div
              layoutId="underline"
              className="underline"
            />
          )}
        </button>
      ))}
    </div>
  );
}

Layout Groups

import { LayoutGroup } from 'framer-motion';

<LayoutGroup>
  <motion.div layout />
  <motion.div layout />
</LayoutGroup>

Layout Transition

<motion.div
  layout
  transition={{
    layout: { duration: 0.3, ease: 'easeOut' },
  }}
/>

Scroll Animations

Scroll-Linked Values

import { motion, useScroll, useTransform } from 'framer-motion';

function ParallaxHeader() {
  const { scrollY } = useScroll();

  const y = useTransform(scrollY, [0, 300], [0, -100]);
  const opacity = useTransform(scrollY, [0, 300], [1, 0]);

  return (
    <motion.header style={{ y, opacity }}>
      <h1>Parallax Header</h1>
    </motion.header>
  );
}

Scroll Progress

function ProgressBar() {
  const { scrollYProgress } = useScroll();

  return (
    <motion.div
      className="progress-bar"
      style={{ scaleX: scrollYProgress }}
    />
  );
}

Scroll Container

function ScrollContainer() {
  const containerRef = useRef(null);
  const { scrollYProgress } = useScroll({
    container: containerRef,
  });

  return (
    <div ref={containerRef} style={{ overflow: 'scroll', height: 400 }}>
      {/* Content */}
    </div>
  );
}

In View Animation

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

function FadeInSection({ children }) {
  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 } : {}}
      transition={{ duration: 0.5 }}
    >
      {children}
    </motion.div>
  );
}

whileInView

<motion.div
  initial={{ opacity: 0 }}
  whileInView={{ opacity: 1 }}
  viewport={{ once: true, amount: 0.5 }}
/>

Animation Controls

useAnimation

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

function Component() {
  const controls = useAnimation();

  async function sequence() {
    await controls.start({ x: 100 });
    await controls.start({ y: 100 });
    await controls.start({ x: 0, y: 0 });
  }

  return (
    <motion.div animate={controls}>
      <button onClick={sequence}>Start Sequence</button>
    </motion.div>
  );
}

Start/Stop

const controls = useAnimation();

// Start animation
controls.start({ x: 100 });

// Start with variants
controls.start('visible');

// Stop animation
controls.stop();

// Set immediately (no animation)
controls.set({ x: 0 });

SVG Animations

Path Drawing

<motion.svg viewBox="0 0 100 100">
  <motion.path
    d="M10 10 L90 10 L90 90 L10 90 Z"
    initial={{ pathLength: 0 }}
    animate={{ pathLength: 1 }}
    transition={{ duration: 2, ease: 'easeInOut' }}
    stroke="#000"
    strokeWidth="2"
    fill="none"
  />
</motion.svg>

Circle Progress

function CircleProgress({ progress }) {
  return (
    <svg viewBox="0 0 100 100">
      <motion.circle
        cx="50"
        cy="50"
        r="40"
        stroke="#3b82f6"
        strokeWidth="8"
        fill="none"
        initial={{ pathLength: 0 }}
        animate={{ pathLength: progress }}
        transition={{ duration: 0.5 }}
        style={{
          rotate: -90,
          transformOrigin: '50% 50%',
        }}
      />
    </svg>
  );
}

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>
  );
}

With AnimatePresence

'use client';

import { AnimatePresence, motion } from 'framer-motion';
import { usePathname } from 'next/navigation';

export default function PageTransition({
  children,
}: {
  children: React.ReactNode;
}) {
  const pathname = usePathname();

  return (
    <AnimatePresence mode="wait">
      <motion.div
        key={pathname}
        initial={{ opacity: 0 }}
        animate={{ opacity: 1 }}
        exit={{ opacity: 0 }}
        transition={{ duration: 0.3 }}
      >
        {children}
      </motion.div>
    </AnimatePresence>
  );
}

Performance

Reduce Motion

import { useReducedMotion } from 'framer-motion';

function Component() {
  const shouldReduceMotion = useReducedMotion();

  return (
    <motion.div
      animate={{ x: shouldReduceMotion ? 0 : 100 }}
      transition={{ duration: shouldReduceMotion ? 0 : 0.5 }}
    />
  );
}

Hardware Acceleration

// Use transform properties for GPU acceleration
<motion.div
  animate={{
    x: 100,        // Good - uses transform
    scale: 1.2,    // Good - uses transform
    opacity: 0.5,  // Good - composited
  }}
/>

// Avoid animating layout properties
<motion.div
  animate={{
    width: 200,    // Avoid - triggers layout
    height: 200,   // Avoid - triggers layout
  }}
/>

Best Practices

  1. Use layout for size changes - Smooth without layout thrashing
  2. Prefer spring for natural feel - More organic than tween
  3. Stagger children in lists - Better visual hierarchy
  4. Use variants for complex animations - Cleaner code
  5. Respect reduced motion - Accessibility

Common Mistakes

MistakeFix
Missing AnimatePresenceWrap exit animations
Animating layout propertiesUse transform instead
Key missing in listsAdd unique keys
No initial stateAdd initial prop
Heavy animationsUse will-change sparingly

Reference Files

Score

Total Score

60/100

Based on repository quality metrics

SKILL.md

SKILL.mdファイルが含まれている

+20
LICENSE

ライセンスが設定されている

+10
説明文

100文字以上の説明がある

0/10
人気

GitHub Stars 100以上

0/15
最近の活動

3ヶ月以内に更新がある

0/10
フォーク

10回以上フォークされている

0/5
Issue管理

オープンIssueが50未満

+5
言語

プログラミング言語が設定されている

+5
タグ

1つ以上のタグが設定されている

0/5

Reviews

💬

Reviews coming soon