loading-states
الگوهای حرفهای برای نمایش بارگذاری، پیشرفت و انتظار در رابط کاربری
این skill برای طراحی و پیادهسازی loading state در عملیات async استفاده میشود؛ از skeleton screen و spinner تا progress bar، optimistic UI و انتقال نرم از placeholder به محتوای واقعی.
با انتخاب الگوی مناسب بر اساس زمان انتظار، بازخورد فوری، انیمیشنهای کمحواسپرتکن و دسترسپذیری بهتری بسازید و تجربهی انتظار کاربر را قابلاعتمادتر کنید.
چه چیزی میگیرید
- انتخاب الگوی مناسب برای زمانها و سناریوهای مختلف بارگذاری
- نمونههای CSS برای skeleton، progress bar و حالتهای indeterminate
- الگوی optimistic UI همراه با بازگشت وضعیت در صورت خطا
- ترنزیشن نرم بین skeleton و محتوای نهایی با AnimatePresence
- راهنمای aria-live، aria-busy و progressbar برای دسترسپذیری
برای چه کاری خوب است
- توسعهدهندگان رابطهای React و CSS که با عملیات async کار میکنند
- طراحی skeleton screen برای صفحات، کارتها و فهرستهای محتوایی
- نمایش پیشرفت آپلود، پردازش یا تولید محتوا
- بهبود تجربهی کاربری در درخواستهای شبکه و فرمهای تعاملی
برای چه کاری نیست
- صفحههایی که هیچ عملیات async یا زمان انتظار قابلتوجهی ندارند
- انیمیشنهای تزئینی مستقل از وضعیت بارگذاری
- وقتی به کتابخانهی کامل مدیریت داده یا پیادهسازی backend نیاز دارید
دربارهٔ این اسکیل
Loading States
Animation patterns that keep users informed and engaged during asynchronous operations. Good loading states reduce perceived wait time and prevent abandonment.
Core Principles
- Immediate feedback - Show something within 100ms
- Progressive disclosure - More detail as wait extends
- Accurate expectations - Determinate when possible
- Minimal distraction - Subtle animation, not attention-grabbing
Pattern Categories
| Pattern | Use When | Duration |
|---|---|---|
| Skeleton | Content structure known | 200ms - 3s |
| Spinner | Unknown structure/duration | 200ms - 10s |
| Progress bar | Duration known/calculable | 500ms - 60s |
| Optimistic UI | Action likely to succeed | Instant |
| Placeholder | Content being generated | 1s+ |
Skeleton Screens
Placeholder that mirrors content structure. Reduces perceived load time by 30-40%.
CSS Implementation
/* Base skeleton */
.skeleton {
background: linear-gradient(
90deg,
var(--skeleton-base) 25%,
var(--skeleton-highlight) 50%,
var(--skeleton-base) 75%
);
background-size: 200% 100%;
animation: skeleton-shimmer 1.5s ease-in-out infinite;
}
:root {
--skeleton-base: #e0e0e0;
--skeleton-highlight: #f0f0f0;
}
@media (prefers-color-scheme: dark) {
:root {
--skeleton-base: #2a2a2a;
--skeleton-highlight: #3a3a3a;
}
}
@keyframes skeleton-shimmer {
0% { background-position: 200% 0; }
100% { background-position: -200% 0; }
}
/* Skeleton shapes */
.skeleton-text { height: 1em; border-radius: 4px; }
.skeleton-title { height: 1.5em; width: 60%; border-radius: 4px; }
.skeleton-avatar { width: 48px; height: 48px; border-radius: 50%; }
.skeleton-image { aspect-ratio: 16/9; border-radius: 8px; }
Progress Indicators
Determinate Progress
/* Linear progress bar */
.progress-bar {
height: 4px;
background: var(--progress-track);
border-radius: 2px;
overflow: hidden;
}
.progress-fill {
height: 100%;
background: var(--progress-fill);
border-radius: 2px;
transition: width 200ms ease-out;
}
Indeterminate Progress
/* Indeterminate linear */
.progress-indeterminate {
height: 4px;
background: var(--progress-track);
overflow: hidden;
position: relative;
}
.progress-indeterminate::after {
content: '';
position: absolute;
top: 0;
left: 0;
height: 100%;
width: 30%;
background: var(--progress-fill);
animation: indeterminate 1.5s ease-in-out infinite;
}
@keyframes indeterminate {
0% { left: -30%; }
100% { left: 100%; }
}
Optimistic UI
Update UI immediately, reconcile with server after. See references/optimistic-patterns.md for implementation details.
Key Pattern
// Optimistic update flow
const handleAction = async () => {
const newValue = !value;
setValue(newValue); // Optimistic update
try {
await api.update(newValue);
} catch {
setValue(!newValue); // Revert on failure
}
};
Content Placeholder Transitions
Smooth transition from skeleton to real content:
<AnimatePresence mode="wait">
{isLoading ? (
<motion.div
key="skeleton"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
transition={{ duration: 0.2 }}
>
{skeleton}
</motion.div>
) : (
<motion.div
key="content"
initial={{ opacity: 0, y: 10 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.3, ease: 'easeOut' }}
>
{children}
</motion.div>
)}
</AnimatePresence>
Timing Guidelines
| Wait Duration | Pattern |
|---|---|
| < 100ms | No indicator needed |
| 100ms - 300ms | Subtle opacity change |
| 300ms - 1s | Skeleton or spinner |
| 1s - 10s | Progress bar if determinable |
| 10s+ | Progress + time estimate |
Accessibility
// Announce loading state to screen readers
<div aria-live="polite" aria-busy={isLoading}>
{isLoading && <span className="sr-only">Loading content...</span>}
{!isLoading && content}
</div>
// Progress bar with aria labels
<div
role="progressbar"
aria-valuenow={progress}
aria-valuemin={0}
aria-valuemax={100}
aria-label="Upload progress"
>
{progress}%
</div>
Common Mistakes
- No immediate feedback - Always show something within 100ms
- Spinner for known content - Use skeleton when structure is known
- Indeterminate when determinate possible - Show progress when calculable
- Forgetting error states - Loading can fail, handle it
- Jarring transitions - Smooth fade from skeleton to content
Related Skills
animation-recipes- Pre-built loading animation recipesaccessibility- WCAG compliance for loading statescomponent-animations- Button loading states, form submissions