Interaction Design

作者 wshobson46891e7e60da無授權條款收錄於 2026年10月8日更新於 2026年10月8日

Design and implement microinteractions, motion design, transitions, and user feedback patterns. Use when adding polish to UI interactions, implementing loading states, or creating delightful user experiences.

AI 產生的概覽

指導介面微互動、動效、轉場與回饋模式的設計與實作。

功能
這個技能為介面中的微互動、動效設計、轉場和使用者回饋提供設計指引與程式碼模式。內容涵蓋時間與緩動規範、載入與骨架畫面狀態、開關、頁面轉場、點擊漣漪回饋、手勢、CSS 關鍵影格,以及減少動效的無障礙處理。它產出參考模式與範例程式碼,而不會執行腳本。
適用情境
適合在為介面互動增加精緻度、實作載入狀態,或設計滑鼠移入、聚焦與手勢回饋時使用。也適合希望統一動效時間並確保動畫無障礙的團隊。
執行需求
沒有腳本,只有說明與參考文件。範例假設使用 React/TypeScript 前端並搭配 Framer Motion 與 CSS,但未說明安裝步驟或憑證需求。

Interaction Design

Create engaging, intuitive interactions through motion, feedback, and thoughtful state transitions that enhance usability and delight users.

When to Use This Skill

  • Adding microinteractions to enhance user feedback
  • Implementing smooth page and component transitions
  • Designing loading states and skeleton screens
  • Creating gesture-based interactions
  • Building notification and toast systems
  • Implementing drag-and-drop interfaces
  • Adding scroll-triggered animations
  • Designing hover and focus states

Core Principles

1. Purposeful Motion

Motion should communicate, not decorate:

  • Feedback: Confirm user actions occurred
  • Orientation: Show where elements come from/go to
  • Focus: Direct attention to important changes
  • Continuity: Maintain context during transitions

2. Timing Guidelines

DurationUse Case
100-150msMicro-feedback (hovers, clicks)
200-300msSmall transitions (toggles, dropdowns)
300-500msMedium transitions (modals, page changes)
500ms+Complex choreographed animations

3. Easing Functions

css
/* 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 */

Quick Start: Button Microinteraction

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

Interaction Patterns

1. Loading States

Skeleton Screens: Preserve layout while loading

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

Progress Indicators: Show determinate progress

tsx
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. State Transitions

Toggle with smooth transition:

tsx
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. Page Transitions

Framer Motion layout animations:

tsx
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. Feedback Patterns

Ripple effect on click:

tsx
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. Gesture Interactions

Swipe to dismiss:

tsx
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 Animation Patterns

Keyframe Animations

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 Transitions

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);}

Accessibility Considerations

css
/* 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;  }}
tsx
function AnimatedComponent() {  const prefersReducedMotion = window.matchMedia(    "(prefers-reduced-motion: reduce)",  ).matches;
  return (    <motion.div      animate={{ opacity: 1 }}      transition={{ duration: prefersReducedMotion ? 0 : 0.3 }}    />  );}

Best Practices

  1. Performance First: Use transform and opacity for smooth 60fps
  2. Reduce Motion Support: Always respect prefers-reduced-motion
  3. Consistent Timing: Use a timing scale across the app
  4. Natural Physics: Prefer spring animations over linear
  5. Interruptible: Allow users to cancel long animations
  6. Progressive Enhancement: Work without JS animations
  7. Test on Devices: Performance varies significantly

Common Issues

  • Janky Animations: Avoid animating width, height, top, left
  • Over-animation: Too much motion causes fatigue
  • Blocking Interactions: Never prevent user input during animations
  • Memory Leaks: Clean up animation listeners on unmount
  • Flash of Content: Use will-change sparingly for optimization

來源與署名

來源:wshobson/agents位於plugins/ui-design/skills/interaction-design提交46891e7

授權條款: 無授權條款

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

檢舉或申請下架