React 声明式动画实战:用 Framer Motion 接管进场、列表与手势

test12026-10-190 次阅读

为什么不直接写 CSS 动画

项目里弹窗、列表、卡片经常需要进场和退场动画。手写 CSS keyframes 要维护 class、监听 transitionend,列表项增删时还要自己算位置。Framer Motion 把这些变成声明式的 props:给元素加 initial、animate、exit,剩下的交给它。

基础进场与退场

用 motion.div 替代普通 div,声明初始态和动画目标态。配合 AnimatePresence 才能播放 exit 动画,否则卸载即消失。

import { motion, AnimatePresence } from "framer-motion";
import { useState } from "react";

export function Notice({ open }: { open: boolean }) {
  return (
    <AnimatePresence>
      {open && (
        <motion.div
          initial={{ opacity: 0, y: -8 }}
          animate={{ opacity: 1, y: 0 }}
          exit={{ opacity: 0, y: -8 }}
          transition={{ duration: 0.2 }}
        >
          这是一条通知
        </motion.div>
      )}
    </AnimatePresence>
  );
}

列表 stagger 一个接一个出现

列表项逐个进场用 variants 的 staggerChildren,父容器控制节奏,子项只需声明 own 动作。

const container = {
  hidden: { opacity: 0 },
  show: { opacity: 1, transition: { staggerChildren: 0.06 } },
};
const item = {
  hidden: { opacity: 0, y: 12 },
  show: { opacity: 1, y: 0 },
};

export function CardList({ data }: { data: string[] }) {
  return (
    <motion.ul variants={container} initial="hidden" animate="show">
      {data.map((t) => (
        <motion.li key={t} variants={item}>{t}</motion.li>
      ))}
    </motion.ul>
  );
}

手势拖拽

drag 属性能让元素被鼠标或手指拖动,dragConstraints 限制范围,拖完自动回弹。

export function DraggableChip() {
  return (
    <motion.div
      drag
      dragConstraints={{ left: 0, right: 0, top: 0, bottom: 0 }}
      dragElastic={0.4}
      whileDrag={{ scale: 1.1 }}
      style={{ width: 80, height: 80, background: "#2563eb", borderRadius: 12 }}
    />
  );
}

落地要点

  • 只给会进场/退场的可见元素套 motion,纯静态结构用普通标签。
  • AnimatePresence 必须包住条件渲染的那一层,且子元素要有稳定 key。
  • 列表动画优先用 Layout 动画(layout 属性)处理重排,别手动算 transform。
  • SSR 项目里 Framer Motion 是纯客户端库,弹窗组件记得标 "use client" 或只在客户端挂载。
T

test1

文章作者

为什么不直接写 CSS 动画 项目里弹窗、列表、卡片经常需要进场和退场动画。手写 CSS keyframes 要维护...

分类
技术
发布时间
2026-10-19
字数
约 1840 字
阅读
0 次