Skip to content
返回设计中心

微交互与动画设计

时间曲线、手势反馈与减弱动效模式,构建愉悦且无障碍的 UI。

组件模式高级

为什么

微交互(按钮涟漪、切换弹簧、加载微光)传达状态变化比文本快 10 倍。但调校差的动画引起前庭不适且卡顿。严谨的运动系统平衡愉悦、性能与无障碍。

何时使用

为状态转换(加载、成功、错误)、手势反馈(滑动、拖拽)和焦点引导添加动效。避免动画装饰元素与内容争夺注意力。

如何做

1. 定义运动令牌 — 持续时间(150/250/400ms)、缓动(ease-out、spring)、错峰 2. 选择库 — React 用 Framer Motion,原生用 Motion One,复杂时间线用 GSAP 3. 用弹簧而非线性 — 自然感,如 transition: { type: "spring", stiffness: 300, damping: 30 } 4. 仅动画 transform + opacity — 避免动画布局属性(width、top)以保 60fps 5. 尊重减弱动效 — 用 useReducedMotion() hook 包裹,回退到瞬时状态变化 6. 编排 — 子元素错峰 delay: i * 0.05,退出动画用 AnimatePresence 7. 性能分析 — React DevTools Profiler + Chrome Performance 标签,目标每帧 <16ms

示例

// motion-tokens.ts — 共享时间词汇 export const motion = { fast: { duration: 0.15, ease: [0.4, 0, 1, 1] as const }, normal: { duration: 0.25, ease: [0.4, 0, 0.2, 1] as const }, spring: { type: "spring", stiffness: 300, damping: 30, mass: 1 }, stagger: { staggerChildren: 0.05 }, } as const; // Toast.tsx — 弹簧入场 + 减弱动效守卫 import { motion, useReducedMotion } from "framer-motion"; function Toast({ message }: { message: string }) { const reduce = useReducedMotion(); return ( <motion.div initial={reduce ? { opacity: 0 } : { opacity: 0, y: 20, scale: 0.95 }} animate={reduce ? { opacity: 1 } : { opacity: 1, y: 0, scale: 1 }} exit={{ opacity: 0, scale: 0.95 }} transition={reduce ? motion.fast : motion.spring} className="rounded-lg bg-slate-900 px-4 py-2 text-white" > {message} </motion.div> ); } // 列表错峰 — 子元素级联进入 <motion.ul variants={{ hidden: {}, show: motion.stagger }} initial="hidden" animate="show"> {items.map((item) => ( <motion.li variants={{ hidden: { opacity: 0, y: 10 }, show: { opacity: 1, y: 0, transition: motion.normal } }}>{item}</motion.li> ))} </motion.ul>

常见问题

  • ⚠动画 width/height/top — 每帧触发布局重算,掉到 20fps
  • ⚠无减弱动效回退 — 前庭用户因视差和弹簧恶心
  • ⚠满地线性缓动 — 机械感,无加减速曲线
  • ⚠每个列表项挂载即动画 — 100 项错峰 = 5s 才出现最后一项

最佳实践

  • ✓仅动画 transform 和 opacity — GPU 合成,无布局抖动
  • ✓错峰上限 5-8 项 — 超过则淡入容器而非级联
  • ✓始终提供 useReducedMotion 分支 — 瞬时状态变化,无动效
  • ✓运动令牌定义一次 — 每个动画引用 motion.normal,非魔法数字

常见问题解答

为什么动画 transform/opacity 而非 width/top?

transform 和 opacity 由 GPU 合成,不触发布局重算。动画 width、top、left 每帧重算布局,掉到 20fps。

如何尊重减弱动效偏好用户?

用 useReducedMotion() hook(Framer Motion)或 matchMedia('(prefers-reduced-motion: reduce)')。设置时跳过动画,瞬时应用终态。

线性 vs 弹簧缓动哪个更好?

弹簧(刚度/阻尼)有加减速自然感并稳定;线性机械。交互元素用弹簧,入场用 ease-out,UI 绝不用线性。

长列表错峰如何不等 5 秒?

错峰上限 5-8 项。超过则整体淡入容器而非逐项级联——100 项 × 50ms = 5s 才出现最后一项。

动画时间应定义在哪?

运动令牌(持续 150/250/400ms、缓动曲线)全应用共享。每个动画引用 motion.normal,绝不用魔法数字——保持时间一致。