מעגל התקדמות (או סרגל התקדמות מעגלי) מספק משוב חזותי על סטטוס המשימה. בניגוד לסרגל התקדמות לינארי, הצורה המעגלית שלו אידיאלית למרכזי בקרה, לרכיבי כרטיסים או לכל מקום שבו המקום מוגבל.
במדריך הזה, מעגל ההתקדמות מוטמע באמצעות:
- שימוש ברכיב
<progress>המקורי כבסיס הסמנטי כדי לוודא שהרכיב נגיש לקוראי מסך ולמשתמשים במקלדת באופן מיידי. - עיצוב הרכיב באמצעות
conic-gradient()ו-mask-image, שמאפשר ליצור טבעת רספונסיבית לחלוטין עם עיצוב מותאם אישית, בלי המורכבות של שינוי נתיב SVG. - שימוש במאפיינים מותאמים אישית של CSS וב-
@propertyכדי לאפשר מעברים חלקים של מילוי ההתקדמות, עם האצה של ה-GPU.
הגישה הזו עדיפה על פתרונות שמבוססים רק על SVG, כי היא משתמשת ברכיב הסמנטי <progress> במקום ב-ARIA, ומשתלבת בקלות רבה יותר עם פריסה, מערכות עיצוב וטיפוגרפיה קיימות.
כדי לטפל במצבי טעינה לא מוגדרים, אפשר לעיין בקטע סימן גרפי שמוצג בזמן טעינה.
הטמעה
1. Markup
משתמשים ב-wrapper כדי להכיל גם את הטבעת החזותית וגם את התוכן האופציונלי במרכז. הרכיב <progress> נשאר מקור המידע האמין הסמנטי. כדי להסתיר את סרגל ההתקדמות המקורי אבל להשאיר אותו נגיש, משתמשים במחלקת שירות (utility class).
<div class="ring-wrapper">
<progress value="75" max="100" aria-label="Task progress" class="progress-ring"></progress>
<!-- Optional: Content to display in the center -->
<div class="ring-content">
75%
</div>
</div>
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;
}
מאגר וטבעת
ה-wrapper מספק את ההקשר של המיקום. הרכיב <progress> מטפל במעבר הצבעים החזותי.
.progress-ring-wrapper {
position: relative;
display: grid;
place-items: center;
}
progress.progress-ring {
--size: 150px;
--thickness: 16px;
--track-color: #f1f5f9;
--fill-color: #3b82f6;
--value: attr(value type(<number>));
width: var(--size);
height: var(--size);
transition: --value 0.4s cubic-bezier(0.4, 0, 0.2, 1);
border-radius: 50%;
background: conic-gradient(
var(--fill-color) calc(var(--value) * 1%),
var(--track-color) 0
);
/* MANDATORY: Clip the background to the border-area. */
background-clip: border-area;
border: var(--thickness) solid transparent;
background-origin: border-box;
}
.progress-ring-content {
/* Positioned in the center of the wrapper */
position: absolute;
}
אפשר גם להשתמש ב-radial-gradient כדי ליצור קצוות מעוגלים.
מעברים חלקים עם @property
כדי להנפיש את מעגל ההתקדמות בצורה חלקה כשהערך משתנה, רושמים את --value כמאפיין מותאם אישית מספרי.
משתמשים עם רגישות לתנועה עלולים לחוש חוסר התמצאות במעבר בין הערכים. כדי לכבד את שאילתת המדיה prefers-reduced-motion, צריך להגדיר משך זמן של 0 שניות (מיידי) כברירת מחדל, ולהגדיר משך זמן ארוך יותר למשתמשים ללא העדפה.
@property --value {
syntax: '<number>';
inherits: true;
initial-value: 0;
}
progress.progress-ring {
transition: --value 0s ease-in-out;
@media (prefers-reduced-motion: no-preference) {
transition-duration: 0.4s;
}
}
3. עדכונים לגבי ההתקדמות
צריך לעדכן את המאפיין value ברכיב <progress> ואת תוכן הטקסט של .progress-ring-content (אם מוצג אחוז) בכל פעם שהערך משתנה.
4. מצב הצלחה אופציונלי
אתם יכולים להשתמש בסלקטור המאפיינים של CSS כדי לעדכן באופן אוטומטי את המראה של הטבעת (למשל, לשנות את הצבע לירוק) כשהמשימה מסתיימת.
/* Change the fill color to green when the progress reaches 100% */
progress.progress-ring[value="100"] {
--fill-color: #10b981;
}
אסטרטגיות חלופיות
אל תוסיפו ערך חלופי בתוך הרכיב <progress>. הוא לא משמש טכנולוגיה מסייעת וכל הדפדפנים המודרניים מתעלמים ממנו.
אנימציה חלופית
אם אין תמיכה ב-@property, הטבעת תעבור לערך החדש באופן מיידי במקום לעבור בצורה חלקה. ברוב המקרים זה בסדר, ולא פוגע בפונקציונליות.
אם המעבר הכרחי, אפשר לבדוק אם יש תמיכה ב-@property ולהשתמש בלולאת requestAnimationFrame() כדי להוסיף --value בדפדפנים ישנים יותר.
בדפדפנים שעדיין לא תומכים ב-background-clip: border-area, צריך לחזור ל-mask-image כדי לרוקן את מרכז הרכיב <progress>.
@supports not (background-clip: border-area) {
mask-image: radial-gradient(
transparent calc(50% - var(--thickness)),
black calc(50% - var(--thickness) + 0.5px)
);
border: 0;
}
בדפדפנים שלא תומכים בפונקציית ה-CSS attr() לאף מאפיין, צריך להשתמש ב-MutationObserver כדי לסנכרן באופן אוטומטי את המאפיין value עם המאפיין המותאם אישית --value.
if (!CSS.supports("width: attr(value type(<number>))")) {
const observer = new MutationObserver(() => {
progress.style.setProperty("--value", progress.getAttribute("value"));
});
observer.observe(progress, {
attributes: true,
attributeFilter: ["value"],
});
}