f8b21af4be
10_Wiki/Topics 대규모 정리: - 오류 캡처/미완성 stub 문서 227개 제거 - 교차폴더 중복 43클러스터 병합 (63파일 → redirect) - 링크명 정규화: 깨진 링크 수정·redirect 직결·개념 매핑 ~2,400건 - 카테고리 MOC 6개 신규 생성 - Graph 섹션 미해결 related-keyword 링크 10,058건 제거 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
7.6 KiB
7.6 KiB
id, title, category, status, canonical_id, aliases, duplicate_of, source_trust_level, confidence_score, verification_status, tags, raw_sources, last_reinforced, github_commit, tech_stack
| id | title | category | status | canonical_id | aliases | duplicate_of | source_trust_level | confidence_score | verification_status | tags | raw_sources | last_reinforced | github_commit | tech_stack | ||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| wiki-2026-0508-micro-interactions | Micro-interactions | 10_Wiki/Topics | verified | self |
|
none | A | 0.9 | applied |
|
2026-05-10 | pending |
|
Micro-interactions
매 한 줄
"매 작은 순간이 매 product 의 personality 를 결정한다.". Micro-interaction 은 매 single task 를 중심으로 한 매 작은 UX moment — toggle, like, error feedback, hover state. Dan Saffer 의 매 4-part model (Trigger / Rules / Feedback / Loops & Modes) 이 표준. 매 well-crafted 매 micro-interaction 은 매 product 를 매 functional → 매 delightful 로 매 끌어올림.
매 핵심
매 Saffer's 4 parts
- Trigger: user 가 시작 (click) 또는 system (notification).
- Rules: 매 무엇이 일어나는지 (state transition).
- Feedback: 매 user 에게 결과 알림 (visual / sound / haptic).
- Loops & Modes: 매 시간에 따른 변화, 매 special state.
매 언제 사용
- Status communication: loading, saving, online/offline.
- Affordance hint: hover, focus, disabled.
- Error prevention: input validation 즉시 feedback.
- Reward: like animation, achievement unlock.
- Branding: 매 unique transition 으로 매 personality.
매 design 원칙
- 빠르게: 매 100-300ms — 매 너무 길면 매 friction.
- Purposeful: 매 deco 아닌 매 information 전달.
- Consistent: 매 동일 action → 매 동일 feedback.
- Respectful:
prefers-reduced-motion존중. - Subtle by default: 매 hero animation 은 매 rare.
매 performance
- 매 transform/opacity 만 사용 (compositor only — GPU).
- 매 layout/paint trigger 회피 (width, height, top, left).
- 매 will-change 의 sparing 사용.
💻 패턴
CSS toggle (transform only)
.toggle {
width: 44px; height: 24px;
background: #ccc;
border-radius: 12px;
transition: background 200ms ease;
position: relative;
cursor: pointer;
}
.toggle::after {
content: '';
position: absolute;
top: 2px; left: 2px;
width: 20px; height: 20px;
background: white;
border-radius: 50%;
transition: transform 200ms cubic-bezier(0.4, 0, 0.2, 1);
}
.toggle.on { background: #4ade80; }
.toggle.on::after { transform: translateX(20px); }
@media (prefers-reduced-motion: reduce) {
.toggle, .toggle::after { transition: none; }
}
Framer Motion (React)
import { motion, AnimatePresence } from 'framer-motion';
function LikeButton({ liked, onToggle }: { liked: boolean; onToggle: () => void }) {
return (
<motion.button
whileTap={{ scale: 0.9 }}
whileHover={{ scale: 1.05 }}
onClick={onToggle}
aria-pressed={liked}
>
<AnimatePresence mode="wait">
<motion.span
key={liked ? 'on' : 'off'}
initial={{ scale: 0.5, opacity: 0 }}
animate={{ scale: 1, opacity: 1 }}
exit={{ scale: 0.5, opacity: 0 }}
transition={{ duration: 0.15 }}
>
{liked ? '❤️' : '🤍'}
</motion.span>
</AnimatePresence>
</motion.button>
);
}
Skeleton loading
const Skeleton = () => (
<motion.div
className="h-4 bg-gray-200 rounded"
animate={{ opacity: [0.5, 1, 0.5] }}
transition={{ duration: 1.5, repeat: Infinity, ease: 'easeInOut' }}
/>
);
Optimistic UI (form submit)
function CommentForm({ onSubmit }: { onSubmit: (text: string) => Promise<void> }) {
const [pending, setPending] = useState(false);
const [error, setError] = useState<string | null>(null);
return (
<form onSubmit={async (e) => {
e.preventDefault();
const text = (e.target as any).text.value;
setPending(true);
setError(null);
try {
await onSubmit(text);
} catch (err) {
setError('Failed. Try again.');
} finally {
setPending(false);
}
}}>
<input name="text" disabled={pending} />
<motion.button
whileTap={{ scale: 0.95 }}
animate={pending ? { opacity: 0.6 } : { opacity: 1 }}
disabled={pending}
>
{pending ? 'Posting…' : 'Post'}
</motion.button>
<AnimatePresence>
{error && (
<motion.div
initial={{ opacity: 0, y: -4 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0 }}
role="alert"
>{error}</motion.div>
)}
</AnimatePresence>
</form>
);
}
Inline validation
function EmailInput() {
const [value, setValue] = useState('');
const [touched, setTouched] = useState(false);
const valid = /\S+@\S+\.\S+/.test(value);
const showError = touched && !valid && value.length > 0;
return (
<div>
<input
type="email"
value={value}
onChange={(e) => setValue(e.target.value)}
onBlur={() => setTouched(true)}
aria-invalid={showError}
/>
<AnimatePresence>
{showError && (
<motion.span
initial={{ opacity: 0, height: 0 }}
animate={{ opacity: 1, height: 'auto' }}
exit={{ opacity: 0, height: 0 }}
className="text-red-600 text-sm"
role="alert"
>Invalid email</motion.span>
)}
</AnimatePresence>
</div>
);
}
Pull-to-refresh (mobile)
import { motion, useMotionValue, useTransform } from 'framer-motion';
function PullToRefresh({ onRefresh }: { onRefresh: () => Promise<void> }) {
const y = useMotionValue(0);
const opacity = useTransform(y, [0, 80], [0, 1]);
const rotate = useTransform(y, [0, 80], [0, 360]);
return (
<motion.div
drag="y"
dragConstraints={{ top: 0, bottom: 100 }}
dragElastic={0.3}
onDragEnd={async (_, info) => {
if (info.offset.y > 80) await onRefresh();
y.set(0);
}}
style={{ y }}
>
<motion.div style={{ opacity, rotate }}>↻</motion.div>
{/* content */}
</motion.div>
);
}
매 결정 기준
| Element | Duration | Easing |
|---|---|---|
| Toggle, hover | 100-200ms | ease-out |
| Modal enter | 200-300ms | cubic-bezier(0.4, 0, 0.2, 1) |
| Modal exit | 150-200ms | cubic-bezier(0.4, 0, 1, 1) |
| Page transition | 300-500ms | ease-in-out |
| Loading shimmer | 1500ms loop | ease-in-out |
기본값: 매 200ms + ease-out + transform/opacity. 매 prefers-reduced-motion 매 항상 존중.
🔗 Graph
- 부모: Interaction Design
- 변형: Animation
- 응용: Optimistic UI
- Adjacent: Framer Motion · Web Animations API · Accessibility (A11y)
🤖 LLM 활용
언제: UI feedback 설계, button/toggle/input 의 polish, error/loading/empty state 의 personality. 언제 X: 매 dense data table, 매 power-user tool — 매 animation 이 매 friction.
❌ 안티패턴
- 너무 긴 duration: 매 500ms+ → 매 sluggish.
- No reduced-motion: 매 vestibular disorder user 에게 매 hostile.
- Decorative only: 매 information 없는 매 animation → 매 cognitive load.
- Inconsistent: 매 같은 action 이 매 다른 feedback.
- Layout-trigger animation: 매 width/height/top → 매 jank.
🧪 검증 / 중복
- Verified (Saffer "Microinteractions", Material Design motion guidelines, Apple HIG, Framer Motion docs, web.dev animations).
- 신뢰도 A.
🕓 Changelog
| 날짜 | 변경 |
|---|---|
| 2026-05-08 | Phase 1 |
| 2026-05-10 | Manual cleanup — Saffer 4-part + Framer Motion / a11y 패턴 |