搜索

PythonReactSEOPDF
GitHub Skill

framer-motion

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.

mgd34msu☆ 3 仓库 Stars0 仓库 Forks

安装与使用

请根据来源仓库和你的 AI 工具说明安装此技能。先阅读技能文件和依赖要求,再添加到工作流。

查看来源 ↗使用指南

技能说明

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

Mistake Fix
Missing AnimatePresence Wrap exit animations
Animating layout properties Use transform instead
Key missing in lists Add unique keys
No initial state Add initial prop
Heavy animations Use will-change sparingly

Reference Files

使用示例

查看来源仓库中的使用示例与配置说明。

查看来源 ↗

来源历史

收录时间
2026-01-20
索引更新时间
2026-01-28
Branch
main

收录与同步日期属于本站记录。源码修改历史请查看来源仓库。

GitHub ↗

chat Comments (0)

chat_bubble_outline

No comments yet. Be the first to share your thoughts!