סמל טעינה (או אינדיקטור לפעילות) מודיע למשתמשים שתהליך מתבצע כשמשך הזמן המדויק לא ידוע. בניגוד למעגל התקדמות, ספינר הוא "לא מוגדר" ובדרך כלל משתמש באנימציה כדי לסמן פעילות.
במדריך הזה, כדי להטמיע ספינר:
- שימוש ברכיב
<progress>המקורי כבסיס הסמנטי. אם משמיטים את המאפייןvalue, הדפדפן מתייחס אליו כאל סרגל התקדמות לא מוגדר, וכך מבטיח שהטכנולוגיות המסייעות יקריאו אותו בצורה נכונה. - עיצוב הרכיב באמצעות
conic-gradient()כדי ליצור "שובל" חזותי ו-background-clip: border-areaכדי לרוקן את המרכז וליצור טבעת. - הנפשה יעילה של הספינר באמצעות טרנספורמציות CSS וכיבוד
prefers-reduced-motionכדי להבטיח חוויה נוחה לכל המשתמשים.
כדי לטפל במשימות מוגדרות עם משך זמן ידוע, אפשר לעיין בקטע מעגל התקדמות.
הטמעה
1. Markup
משתמשים ברכיב <progress> המקורי גם כמקור האמת הסמנטי וגם כרכיב החזותי. בלי המאפיין value, הוא לא מוגדר באופן משתמע.
<progress aria-label="Loading" class="loading-spinner"></progress>
2. סגנונות
הסתרת ממשק המשתמש המקורי
כדי לעצב את הרכיב <progress> כספינר, קודם צריך להסתיר את עיצוב ברירת המחדל של הדפדפן לסרגלי התקדמות לא מוגדרים.
/* Hide native bars */
progress.loading-spinner:indeterminate::-webkit-progress-bar {
display: none;
background: none;
}
progress.loading-spinner:indeterminate::-webkit-progress-value {
display: none;
background: none;
}
progress.loading-spinner:indeterminate::-moz-progress-bar {
display: none;
background: none;
}
progress.loading-spinner:indeterminate::slider-fill {
display: none;
background: none;
}
טבעת ושובל של ספינר
הספינר משתמש ב-conic-gradient כדי ליצור שובל ויזואלי.
@property --arc-start {
syntax: "<angle>";
inherits: false;
initial-value: 0deg;
}
@property --arc-end {
syntax: "<angle>";
inherits: false;
initial-value: 0deg;
}
/* Use a custom property to conditionally include the dash animation */
@property --progress-dash-animation {
syntax: "*";
inherits: false;
initial-value: , progress-dash 3s ease-in-out infinite;
}
progress.loading-spinner:indeterminate {
--_from: calc(90deg + var(--arc-start, 0deg));
--_to: calc(90deg + var(--arc-end, 158deg));
--size: 40px;
--thickness: 2px;
--spinner-color: #3b82f6;
--track-color: #e2e5e7;
--spinner-duration: 1.5s;
--_used-spinner-duration: var(--spinner-duration);
--spinner-timing: linear;
position: relative;
width: var(--size);
height: var(--size);
border-radius: 50%;
appearance: none;
/* Create the fading trail with dynamic angles */
background: conic-gradient(
from var(--_from),
var(--spinner-color) calc(var(--_to) - var(--_from)),
transparent 0
)
var(--track-color);
@supports (background-clip: border-area) {
background-clip: border-area;
border: var(--thickness) solid transparent;
background-origin: border-box;
}
/* ... fallback for background-clip: border-area ... */
/* The dash animation is only included if @property is supported */
animation:
progress-spin var(--_used-spinner-duration) linear infinite
var(--progress-dash-animation, );
}
@keyframes progress-spin {
to {
rotate: 1turn;
}
}
@keyframes progress-dash {
from {
--arc-start: 0deg;
--arc-end: 3deg;
}
50% {
--arc-start: 100deg;
--arc-end: 358deg;
}
to {
--arc-start: 360deg;
--arc-end: 363deg;
}
}
התחשבות בהעדפות לגבי תנועה
משתמשים עם רגישות לתנועה עלולים לחוש חוסר התמצאות בגלל אלמנטים שמסתובבים במהירות. תמיד צריך לכבד את שאילתת המדיה prefers-reduced-motion. מגדירים את המאפיין הפנימי --_used-spinner-duration כדי לשנות את הערך של --spinner-duration של המשתמש.
@media (prefers-reduced-motion: reduce) {
.loading-spinner {
/* Slow down the animation significantly rather than stopping it entirely,
so the user still knows that the process is active. */
--_used-spinner-duration: 6s;
}
}
אסטרטגיות חלופיות
אם יש תמיכה ב-@property, האנימציה של המקף נכללת באופן אוטומטי דרך initial-value של המאפיין --progress-dash-animation. בדפדפנים שלא תומכים ב-@property, רישום המאפיין לא מתבצע והאנימציה חוזרת לסיבוב פשוט. לא נדרש JavaScript בשביל החלופה הזו.
בדפדפנים שעדיין לא תומכים ב-background-clip: border-area, צריך להשתמש ב-mask-image כדי לרוקן את המרכז.
/* Fallback: use mask-image to create the ring */
@supports not (background-clip: border-area) {
--clip-boundary: calc(100% - var(--thickness));
mask-image: radial-gradient(
farthest-side,
transparent var(--clip-boundary),
black var(--clip-boundary)
);
border: 0;
}