mcpField
I
interaction-design

interaction-design

github.com/wshobson设计与 UI41.9k16.4k 次安装2 周前更新
代码仓库

关于 interaction-design

通过动效、反馈和状态转换设计界面交互的技能,面向 UI 设计者与开发者,提供加载状态、手势等交互的实现指导,并涵盖无障碍与性能考量。

主要功能

  1. 01微交互与反馈模式
  2. 02页面和组件转换
  3. 03加载状态与进度指示
  4. 04手势及拖放交互指导
  5. 05减少动态效果与性能实践

适用场景

  1. 01为按钮和控件添加反馈
  2. 02设计加载与进度状态
  3. 03构建滑动或拖放交互
  4. 04规划无障碍页面转换

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/wshobson/agents --skill interaction-design

安装后在 Claude Code 中输入/interaction-design确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

references/
animation-libraries.md
microinteraction-patterns.md
scroll-animations.md

详细介绍

交互设计

通过动效、反馈和周到的状态过渡,创建引人入胜、直观的交互,从而提升可用性并让用户感到愉悦。

何时使用此技能

  • 添加微交互以增强用户反馈
  • 实现流畅的页面和组件过渡
  • 设计加载状态和骨架屏
  • 创建基于手势的交互
  • 构建通知和轻提示系统
  • 实现拖放界面
  • 添加滚动触发动画
  • 设计悬停和聚焦状态

核心原则

1. 有目的的动效

动效应当用于传达信息,而非装饰:

  • 反馈:确认用户操作已发生
  • 方向:展示元素的来源和去向
  • 聚焦:将注意力引导至重要变化
  • 连续性:在过渡期间保持上下文
2. 时长指南
时长用例
100-150ms微反馈(悬停、点击)
200-300ms小型过渡(开关、下拉菜单)
300-500ms中型过渡(模态框、页面切换)
500ms+复杂的编排动画
3. 缓动函数
/* Common easings */
--ease-out: cubic-bezier(0.16, 1, 0.3, 1); /* Decelerate - entering */
--ease-in: cubic-bezier(0.55, 0, 1, 0.45); /* Accelerate - exiting */
--ease-in-out: cubic-bezier(0.65, 0, 0.35, 1); /* Both - moving between */
--spring: cubic-bezier(0.34, 1.56, 0.64, 1); /* Overshoot - playful */

快速开始:按钮微交互

import { motion } from "framer-motion";

export function InteractiveButton({ children, onClick }) {
  return (
    <motion.button
      onClick={onClick}
      whileHover={{ scale: 1.02 }}
      whileTap={{ scale: 0.98 }}
      transition={{ type: "spring", stiffness: 400, damping: 17 }}
      className="px-4 py-2 bg-blue-600 text-white rounded-lg"
    >
      {children}
    </motion.button>
  );
}

交互模式

1. 加载状态

骨架屏:在加载时保留布局

function CardSkeleton() {
  return (
    <div className="animate-pulse">
      <div className="h-48 bg-gray-200 rounded-lg" />
      <div className="mt-4 h-4 bg-gray-200 rounded w-3/4" />
      <div className="mt-2 h-4 bg-gray-200 rounded w-1/2" />
    </div>
  );
}

进度指示器:显示确定性进度

function ProgressBar({ progress }: { progress: number }) {
  return (
    <div className="h-2 bg-gray-200 rounded-full overflow-hidden">
      <motion.div
        className="h-full bg-blue-600"
        initial={{ width: 0 }}
        animate={{ width: `${progress}%` }}
        transition={{ ease: "easeOut" }}
      />
    </div>
  );
}
2. 状态过渡

使用平滑过渡进行切换:

function Toggle({ checked, onChange }) {
  return (
    <button
      role="switch"
      aria-checked={checked}
      onClick={() => onChange(!checked)}
      className={`
        relative w-12 h-6 rounded-full transition-colors duration-200
        ${checked ? "bg-blue-600" : "bg-gray-300"}
      `}
    >
      <motion.span
        className="absolute top-1 left-1 w-4 h-4 bg-white rounded-full shadow"
        animate={{ x: checked ? 24 : 0 }}
        transition={{ type: "spring", stiffness: 500, damping: 30 }}
      />
    </button>
  );
}
3. 页面过渡

Framer Motion 布局动画:

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

function PageTransition({ children, key }) {
  return (
    <AnimatePresence mode="wait">
      <motion.div
        key={key}
        initial={{ opacity: 0, y: 20 }}
        animate={{ opacity: 1, y: 0 }}
        exit={{ opacity: 0, y: -20 }}
        transition={{ duration: 0.3 }}
      >
        {children}
      </motion.div>
    </AnimatePresence>
  );
}
4. 反馈模式

点击时的涟漪效果:

function RippleButton({ children, onClick }) {
  const [ripples, setRipples] = useState([]);

  const handleClick = (e) => {
    const rect = e.currentTarget.getBoundingClientRect();
    const ripple = {
      x: e.clientX - rect.left,
      y: e.clientY - rect.top,
      id: Date.now(),
    };
    setRipples((prev) => [...prev, ripple]);
    setTimeout(() => {
      setRipples((prev) => prev.filter((r) => r.id !== ripple.id));
    }, 600);
    onClick?.(e);
  };

  return (
    <button onClick={handleClick} className="relative overflow-hidden">
      {children}
      {ripples.map((ripple) => (
        <span
          key={ripple.id}
          className="absolute bg-white/30 rounded-full animate-ripple"
          style={{ left: ripple.x, top: ripple.y }}
        />
      ))}
    </button>
  );
}
5. 手势交互

滑动关闭:

function SwipeCard({ children, onDismiss }) {
  return (
    <motion.div
      drag="x"
      dragConstraints={{ left: 0, right: 0 }}
      onDragEnd={(_, info) => {
        if (Math.abs(info.offset.x) > 100) {
          onDismiss();
        }
      }}
      className="cursor-grab active:cursor-grabbing"
    >
      {children}
    </motion.div>
  );
}

CSS 动画模式

关键帧动画
@keyframes fadeIn {
  from {
    opacity: 0;
    transform: translateY(10px);
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}

@keyframes pulse {
  0%,
  100% {
    opacity: 1;
  }
  50% {
    opacity: 0.5;
  }
}

@keyframes spin {
  to {
    transform: rotate(360deg);
  }
}

.animate-fadeIn {
  animation: fadeIn 0.3s ease-out;
}
.animate-pulse {
  animation: pulse 2s ease-in-out infinite;
}
.animate-spin {
  animation: spin 1s linear infinite;
}
CSS 过渡
.card {
  transition:
    transform 0.2s ease-out,
    box-shadow 0.2s ease-out;
}

.card:hover {
  transform: translateY(-4px);
  box-shadow: 0 12px 24px rgba(0, 0, 0, 0.1);
}

无障碍考量

/* Respect user motion preferences */
@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
  }
}
function AnimatedComponent() {
  const prefersReducedMotion = window.matchMedia(
    "(prefers-reduced-motion: reduce)",
  ).matches;

  return (
    <motion.div
      animate={{ opacity: 1 }}
      transition={{ duration: prefersReducedMotion ? 0 : 0.3 }}
    />
  );
}

最佳实践

  1. 性能优先:使用 transform 和 opacity 以实现流畅的 60fps
  2. 减少动效支持:始终尊重 prefers-reduced-motion
  3. 一致的时长:在整个应用中使用统一的时长刻度
  4. 自然物理:优先使用弹簧动画而非线性动画
  5. 可中断:允许用户取消长时间动画
  6. 渐进增强:在没有 JS 动画的情况下也能正常工作
  7. 在设备上测试:性能差异可能非常显著

常见问题

  • 动画卡顿:避免对 width、height、top、left 进行动画
  • 过度动画:过多的动效会导致疲劳
  • 阻塞交互:绝不要在动画期间阻止用户输入
  • 内存泄漏:在卸载时清理动画监听器
  • 内容闪烁:谨慎使用 will-change 进行优化

评论

支持 Markdown