Framer Motion Animator

patricio0312rev/skills/frontend/framer-motion-animator

作者 patricio0312rev79ea6af48569無授權條款62 個星標收錄於 2026年10月8日更新於 2026年10月8日儲存庫9 個月前更新

Creates smooth animations and micro-interactions using Framer Motion including page transitions, gestures, scroll-based animations, and orchestrated sequences. Use when users request "add animation", "framer motion", "page transition", "animate component", or "micro-interactions".

AI 產生的概覽

指導在 React 介面中使用 Framer Motion 實作動畫與微互動。

功能
此技能提供使用 Framer Motion 在 React 中建立動畫的說明與程式碼模式,涵蓋進入與退出轉場、懸停與點擊手勢、拖曳與滑動互動、捲動觸發效果以及交錯序列。它提供可重複使用的元件範例,例如動畫容器與清單、轉場預設以及減少動態效果的處理方式。交付內容是實作指引與範例程式碼,而非產生的檔案。
適用情境
當使用者要求為 React 介面加入動畫、頁面轉場、微互動或手勢與捲動效果時使用。也適用於提到 Framer Motion 或為元件加入動畫的需求。
執行需求
需要一個已安裝 framer-motion npm 套件的 React 專案;不附帶指令碼,僅提供說明與程式碼範例。

Framer Motion Animator

Build delightful animations and interactions with Framer Motion's declarative API.

Core Workflow

  1. Identify animation needs: Entrance, exit, hover, gestures
  2. Choose animation type: Simple, variants, gestures, layout
  3. Define motion values: Opacity, scale, position, rotation
  4. Add transitions: Duration, easing, spring physics
  5. Orchestrate sequences: Stagger, delay, parent-child
  6. Optimize performance: GPU-accelerated properties

Installation

bash
npm install framer-motion

Basic Animations

Simple Animation

tsx
import { motion } from 'framer-motion';
// Animate on mountexport 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 hoverexport 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

tsx
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 */}          <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

tsx
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

tsx
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

tsx
// 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

tsx
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

tsx
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

tsx
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

tsx
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

tsx
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)

tsx
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

tsx
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

tsx
// components/AnimatedContainer.tsximport { 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

tsx
// components/AnimatedList.tsximport { 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

tsx
// lib/transitions.tsexport 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

tsx
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

  1. Use GPU-accelerated properties: opacity, transform (not width, height)
  2. Add layout for smooth resizing: Automatic layout animations
  3. Use AnimatePresence: For exit animations
  4. Prefer springs: More natural than tween for UI
  5. Respect reduced motion: Use useReducedMotion hook
  6. Avoid animating layout thrashing: Don't animate top, left, width
  7. Use layoutId: For shared element transitions
  8. 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

來源與署名

來源:patricio0312rev/skills位於frontend/framer-motion-animator提交79ea6af

授權條款: 無授權條款

內容歸原作者所有。SourceWeft 從公開儲存庫中收錄這些內容。

檢舉或申請下架