סקירה כללית של אופן בניית סרגל טעינה נגיש ועם צבעים שניתנים להתאמה באמצעות רכיב <progress>
.
בפוסט הזה אני רוצה לשתף את המחשבות שלי לגבי בניית סרגל טעינה נגיש עם התאמה לצבעים באמצעות רכיב <progress>
. אפשר לנסות את ההדגמה ולראות את המקור.
אם אתם מעדיפים לצפות בסרטון, הנה גרסת YouTube של הפוסט הזה:
סקירה כללית
רכיב <progress>
מספק למשתמשים משוב חזותי ומשוב קולי לגבי השלמת הצפייה. המשוב החזותי הזה חשוב לתרחישים כמו: התקדמות במילוי טופס, הצגת מידע על הורדה או העלאה, או אפילו הצגה של כמות ההתקדמות כלא ידועה, אבל העבודה עדיין פעילה.
האתגר הזה של ממשק משתמש גרפי פעל עם אלמנט ה-HTML הקיים <progress>
כדי לחסוך מאמץ מסוים בנגישות. הצבעים והפריסות מרחיבים את גבולות ההתאמה האישית של הרכיב המובנה, כדי לעדכן אותו ולשפר את ההתאמה שלו למערכות עיצוב.

Markup
בחרתי להוסיף את האלמנט <progress>
בתוך <label>
כדי לדלג על מאפייני הקשר המפורשים ולבחור בקשר משתמע.
סימנתי גם רכיב אב שמושפע ממצב הטעינה, כדי שטכנולוגיות של קוראי מסך יוכלו להעביר את המידע הזה למשתמש.
<progress></progress>
אם אין value
, מצב ההתקדמות של הרכיב הוא לא מוגדר.
ערך ברירת המחדל של המאפיין max
הוא 1, ולכן ערכי ההתקדמות הם בין 0 ל-1. לדוגמה, אם מגדירים את max
ל-100, הטווח יהיה 0-100. בחרתי להישאר במסגרת המגבלות של 0 ו-1, ולתרגם את ערכי ההתקדמות ל-0.5 או ל-50%.
התקדמות עטופה בתווית
במערכת יחסים מרומזת, רכיב התקדמות עטוף בתווית באופן הבא:
<label>Loading progress<progress></progress></label>
בהדגמה שלי בחרתי לכלול את התווית רק לקוראי מסך.
כדי לעשות את זה, צריך להוסיף את הטקסט של התווית בין התגים <span>
ולהחיל עליו סגנונות מסוימים כדי שהוא לא יוצג על המסך:
<label>
<span class="sr-only">Loading progress</span>
<progress></progress>
</label>
עם ה-CSS הנלווה הבא מ-WebAIM:
.sr-only {
clip: rect(1px, 1px, 1px, 1px);
clip-path: inset(50%);
height: 1px;
width: 1px;
margin: -1px;
overflow: hidden;
padding: 0;
position: absolute;
}
האזור שמושפע מהתקדמות הטעינה
אם הראייה שלכם תקינה, קל לכם לשייך את אינדיקטור ההתקדמות לרכיבים ולשטחים בדף שקשורים אליו, אבל למשתמשים עם לקויות ראייה זה לא כל כך ברור. כדי לשפר את זה, צריך להקצות את המאפיין aria-busy
לרכיב העליון ביותר שישתנה כשהטעינה תסתיים.
בנוסף, צריך לציין את הקשר בין ההתקדמות לבין אזור הטעינה באמצעות aria-describedby
.
<main id="loading-zone" aria-busy="true">
…
<progress aria-describedby="loading-zone"></progress>
</main>
ב-JavaScript, מעבירים את המתג aria-busy
למצב true
בתחילת המשימה, ולמצב false
בסיום.
הוספות של מאפייני Aria
התפקיד המרומז של רכיב <progress>
הוא progressbar
, אבל הגדרתי אותו במפורש כדי שדפדפנים שלא תומכים בתפקיד המרומז הזה יוכלו להשתמש בו. הוספתי גם את המאפיין indeterminate
כדי להעביר את הרכיב למצב לא ידוע באופן מפורש, וזה ברור יותר מאשר לראות שלא הוגדר מאפיין value
לרכיב.
<label>
Loading
<progress
indeterminate
role="progressbar"
aria-describedby="loading-zone"
tabindex="-1"
>unknown</progress>
</label>
משתמשים ב-tabindex="-1"
כדי להגדיר את רכיב ההתקדמות כרכיב שאפשר להתמקד בו מ-JavaScript. הנתון הזה חשוב לטכנולוגיית קורא המסך, כי אם המיקוד של ההתקדמות ישתנה כשההתקדמות תשתנה, המשתמש ישמע מהי רמת ההתקדמות המעודכנת.
סגנונות
קשה לעצב את רכיב ההתקדמות. רכיבי HTML מובנים כוללים חלקים מיוחדים ומוסתרים, שלפעמים קשה לבחור אותם, ולרוב אפשר להגדיר רק קבוצה מוגבלת של מאפיינים.
פריסה
סגנונות הפריסה נועדו לאפשר גמישות מסוימת בגודל של רכיב ההתקדמות ובמיקום התווית. נוסף מצב השלמה מיוחד שיכול להיות רמז חזותי שימושי, אבל הוא לא חובה.
<progress>
פריסה
הרוחב של רכיב ההתקדמות לא משתנה, כדי שהוא יוכל להתכווץ ולהתרחב בהתאם למקום שנדרש בעיצוב. הסגנונות המובנים מוסרים על ידי הגדרת appearance
ו-border
ל-none
. הפעולה הזו מתבצעת כדי שהרכיב יוכל להיות מנורמל בדפדפנים שונים, כי לכל דפדפן יש סגנונות משלו לרכיב.
progress {
--_track-size: min(10px, 1ex);
--_radius: 1e3px;
/* reset */
appearance: none;
border: none;
position: relative;
height: var(--_track-size);
border-radius: var(--_radius);
overflow: hidden;
}
הערך של 1e3px
עבור _radius
משתמש ברישום מדעי כדי לבטא מספר גדול, ולכן border-radius
תמיד מעוגל. היא שוות ערך ל-1000px
. אני אוהב להשתמש בערך הזה כי המטרה שלי היא להשתמש בערך גדול מספיק שאוכל להגדיר אותו ולשכוח ממנו (וגם קל יותר לכתוב אותו מאשר 1000px
). קל גם להגדיל אותו עוד יותר אם צריך: פשוט משנים את 3 ל-4, ואז 1e4px
שווה ל-10000px
.
הסגנון overflow: hidden
נמצא בשימוש והוא שנוי במחלוקת. הוא הקל על כמה דברים, כמו העברת ערכי border-radius
למסלול ולרכיבי המילוי של המסלול, אבל הוא גם מנע מרכיבי צאצא של רכיב ההתקדמות להתקיים מחוץ לרכיב. אפשר ליצור איטרציה נוספת של רכיב ההתקדמות המותאם אישית הזה בלי overflow: hidden
, וזה עשוי לפתוח הזדמנויות ליצירת אנימציות או מצבי השלמה טובים יותר.
ההתקדמות הושלמה
סלקטורים ב-CSS מבצעים כאן את העבודה הקשה: הם משווים בין הערך לבין הערך המקסימלי, ואם הם זהים, ההתקדמות הושלמה. בסיום, נוצר פסאודו-אלמנט שמצורף לסוף רכיב ההתקדמות, ומספק רמז ויזואלי נוסף לסיום.
progress:not([max])[value="1"]::before,
progress[max="100"][value="100"]::before {
content: "✓";
position: absolute;
inset-block: 0;
inset-inline: auto 0;
display: flex;
align-items: center;
padding-inline-end: max(calc(var(--_track-size) / 4), 3px);
color: white;
font-size: calc(var(--_track-size) / 1.25);
}
צבע
הדפדפן מביא צבעים משלו לאלמנט ההתקדמות, והוא מותאם למצב בהיר ולמצב כהה באמצעות מאפיין CSS אחד בלבד. אפשר להשתמש בבוררים מיוחדים שספציפיים לדפדפן כדי להרחיב את ההגדרה הזו.
סגנונות בהירים וכהים לדפדפן
כדי להגדיר באתר רכיב <progress>
שמותאם לעיצוב כהה ובהיר, צריך להוסיף את התג color-scheme
.
progress {
color-scheme: light dark;
}
סרגל התקדמות בצבע אחיד של נכס יחיד
כדי להוסיף גוון לרכיב <progress>
, משתמשים ב-accent-color
.
progress {
accent-color: rebeccapurple;
}
שימו לב שצבע הרקע של הרצועה משתנה מבהיר לכהה בהתאם לaccent-color
. הדפדפן מוודא שיש ניגודיות מתאימה: זה די מגניב.
צבעים בהירים וכהים בהתאמה אישית מלאה
מגדירים שני מאפיינים מותאמים אישית ברכיב <progress>
, אחד לצבע של פס ההתקדמות ואחד לצבע של ההתקדמות בפס. בתוך שאילתת המדיה
prefers-color-scheme
, מציינים ערכי צבע חדשים עבור הרצועה וההתקדמות של הרצועה.
progress {
--_track: hsl(228 100% 90%);
--_progress: hsl(228 100% 50%);
}
@media (prefers-color-scheme: dark) {
progress {
--_track: hsl(228 20% 30%);
--_progress: hsl(228 100% 75%);
}
}
סגנונות התמקדות
קודם לכן נתנו לרכיב אינדקס שלילי של כרטיסייה כדי שאפשר יהיה להתמקד בו באופן פרוגרמטי. אפשר להשתמש ב-:focus-visible
כדי להתאים אישית את הדגשת המיקוד ולבחור בסגנון הדגשת המיקוד החכם יותר. במקרה כזה, לחיצה על העכבר והמיקוד לא יציגו את טבעת המיקוד, אבל לחיצות על המקלדת כן. בסרטון הזה ב-YouTube יש הסבר מפורט יותר, וכדאי לצפות בו.
progress:focus-visible {
outline-color: var(--_progress);
outline-offset: 5px;
}
סגנונות מותאמים אישית בדפדפנים שונים
כדי להתאים אישית את הסגנונות, בוחרים את החלקים של רכיב <progress>
שכל דפדפן חושף. השימוש ברכיב progress הוא תג יחיד, אבל הוא מורכב מכמה רכיבים משניים שנחשפים באמצעות פסאודו סלקטורים של CSS. אם תפעילו את ההגדרה הזו, כלי הפיתוח של Chrome יציגו לכם את הרכיבים האלה:
- לוחצים לחיצה ימנית על הדף ובוחרים באפשרות בדיקת המרכיב כדי להציג את כלי הפיתוח.
- לוחצים על סמל גלגל השיניים של ההגדרות בפינה השמאלית העליונה של חלון כלי הפיתוח.
- בקטע Elements, מאתרים את תיבת הסימון Show user agent shadow DOM ומסמנים אותה.
סגנונות של Safari ו-Chromium
דפדפנים מבוססי WebKit כמו Safari ו-Chromium חושפים את ::-webkit-progress-bar
ו-::-webkit-progress-value
, שמאפשרים להשתמש בקבוצת משנה של CSS. בשלב הזה, מגדירים את background-color
באמצעות המאפיינים המותאמים אישית שנוצרו קודם, שמותאמים למצב בהיר ולמצב כהה.
/* Safari/Chromium */
progress[value]::-webkit-progress-bar {
background-color: var(--_track);
}
progress[value]::-webkit-progress-value {
background-color: var(--_progress);
}
סגנונות Firefox
Firefox חושף רק את ::-moz-progress-bar
pseudo selector ברכיב <progress>
. זה גם אומר שאי אפשר לצבוע את הטראק ישירות.
/* Firefox */
progress[value]::-moz-progress-bar {
background-color: var(--_progress);
}
שימו לב שצבע הרצועה ב-Firefox מוגדר מ-accent-color
, ואילו ב-iOS Safari הרצועה בצבע כחול בהיר. המצב דומה במצב הכהה: ל-Firefox יש פס כהה אבל לא הצבע המותאם אישית שהגדרנו, והוא פועל בדפדפנים שמבוססים על Webkit.
Animation
כשעובדים עם פסאודו סלקטורים מובנים בדפדפן, לרוב מדובר בקבוצה מוגבלת של מאפייני CSS מותרים.
הנפשה של הטראק מתמלא
הוספת מעבר ל-inline-size
של רכיב ההתקדמות פועלת ב-Chromium אבל לא ב-Safari. בנוסף, דפדפן Firefox לא משתמש במאפיין מעבר ב-::-moz-progress-bar
שלו.
/* Chromium Only 😢 */
progress[value]::-webkit-progress-value {
background-color: var(--_progress);
transition: inline-size .25s ease-out;
}
הנפשת המצב :indeterminate
כאן אני קצת יותר יצירתי כדי שאוכל לספק אנימציה. נוצר פסאודו-אלמנט ל-Chromium ומוחל עליו מעבר צבעים עם אנימציה הלוך ושוב בכל שלושת הדפדפנים.
המאפיינים המותאמים אישית
מאפיינים בהתאמה אישית מצוינים להרבה דברים, אבל אחד מהדברים האהובים עליי הוא פשוט לתת שם לערך CSS שנראה קסום. הנה linear-gradient
די מורכב,
אבל עם שם נחמד. המטרה והתרחישים לדוגמה שלו ברורים.
progress {
--_indeterminate-track: linear-gradient(to right,
var(--_track) 45%,
var(--_progress) 0%,
var(--_progress) 55%,
var(--_track) 0%
);
--_indeterminate-track-size: 225% 100%;
--_indeterminate-track-animation: progress-loading 2s infinite ease;
}
מאפיינים בהתאמה אישית גם עוזרים לשמור על קוד DRY, כי שוב, אי אפשר לקבץ את הסלקטורים האלה שספציפיים לדפדפן.
תמונות המפתח
המטרה היא ליצור אנימציה אינסופית שחוזרת על עצמה שוב ושוב. פריים המפתח של ההתחלה והסיום יוגדרו ב-CSS. כדי ליצור אנימציה שחוזרת שוב ושוב לנקודת ההתחלה, צריך רק תמונת מפתח אחת – תמונת המפתח האמצעית בנקודה 50%
.
@keyframes progress-loading {
50% {
background-position: left;
}
}
טירגוט של כל דפדפן
לא בכל דפדפן אפשר ליצור פסאודו-אלמנטים באלמנט <progress>
עצמו או להנפיש את סרגל ההתקדמות. יותר דפדפנים תומכים בהנפשת הרצועה מאשר ברכיב פסאודו, לכן אני משדרג מרכיבי פסאודו כבסיס להנפשת פסים.
רכיב פסאודו ב-Chromium
Chromium מאפשר שימוש בפסאודו-אלמנט: ::after
עם מיקום כדי לכסות את האלמנט. נעשה שימוש במאפיינים מותאמים אישית לא מוגדרים, והאנימציה של התנועה קדימה ואחורה פועלת בצורה טובה מאוד.
progress:indeterminate::after {
content: "";
inset: 0;
position: absolute;
background: var(--_indeterminate-track);
background-size: var(--_indeterminate-track-size);
background-position: right;
animation: var(--_indeterminate-track-animation);
}
סרגל התקדמות ב-Safari
ב-Safari, המאפיינים המותאמים אישית והאנימציה מוחלים על פס ההתקדמות של פסאודו-אלמנט:
progress:indeterminate::-webkit-progress-bar {
background: var(--_indeterminate-track);
background-size: var(--_indeterminate-track-size);
background-position: right;
animation: var(--_indeterminate-track-animation);
}
סרגל ההתקדמות של Firefox
ב-Firefox, המאפיינים המותאמים אישית והאנימציה מוחלים גם על פס ההתקדמות של פסאודו-אלמנט:
progress:indeterminate::-moz-progress-bar {
background: var(--_indeterminate-track);
background-size: var(--_indeterminate-track-size);
background-position: right;
animation: var(--_indeterminate-track-animation);
}
JavaScript
ל-JavaScript יש תפקיד חשוב ברכיב <progress>
. היא שולטת בערך שנשלח לרכיב ומוודאת שיש מספיק מידע במסמך לקוראי מסך.
const state = {
val: null
}
בהדגמה יש לחצנים לשליטה בהתקדמות. הם מעדכנים את state.val
ואז קוראים לפונקציה לעדכון DOM.
document.querySelector('#complete').addEventListener('click', e => {
state.val = 1
setProgress()
})
setProgress()
הפונקציה הזו היא המקום שבו מתבצעת תזמור של ממשק המשתמש וחוויית המשתמש. כדי להתחיל, יוצרים פונקציה setProgress()
. לא נדרשים פרמטרים כי יש לו גישה לאובייקט state
, לרכיב ההתקדמות ולאזור <main>
.
const setProgress = () => {
}
הגדרת סטטוס הטעינה באזור <main>
בהתאם לסטטוס ההתקדמות (הושלמה או לא הושלמה), צריך לעדכן את המאפיין aria-busy
של רכיב <main>
:
const setProgress = () => {
zone.setAttribute('aria-busy', state.val < 1)
}
ניקוי מאפיינים אם סכום הטעינה לא ידוע
אם הערך לא ידוע או לא מוגדר, null
בהקשר הזה, צריך להסיר את המאפיינים value
ו-aria-valuenow
. הסטטוס של <progress>
יהפוך ללא ידוע.
const setProgress = () => {
zone.setAttribute('aria-busy', state.val < 1)
if (state.val === null) {
progress.removeAttribute('aria-valuenow')
progress.removeAttribute('value')
progress.focus()
return
}
}
פתרון בעיות בחישובים עשרוניים ב-JavaScript
מכיוון שבחרתי להשתמש בערך ברירת המחדל של ההתקדמות, שהוא 1, הפונקציות להגדלה ולהקטנה של ההדגמה משתמשות במתמטיקה עשרונית. JavaScript ושפות אחרות לא תמיד מצטיינות בזה.
הנה פונקציית roundDecimals()
שתסיר את החלקים המיותרים מתוצאת החישוב:
const roundDecimals = (val, places) =>
+(Math.round(val + "e+" + places) + "e-" + places)
מעגלים את הערך כדי שיהיה אפשר להציג אותו ולקרוא אותו:
const setProgress = () => {
zone.setAttribute('aria-busy', state.val < 1)
if (state.val === null) {
progress.removeAttribute('aria-valuenow')
progress.removeAttribute('value')
progress.focus()
return
}
const val = roundDecimals(state.val, 2)
const valPercent = val * 100 + "%"
}
הגדרת ערך לקוראי מסך ולמצב הדפדפן
הערך משמש בשלושה מיקומים ב-DOM:
- המאפיין
value
של הרכיב<progress>
. - המאפיין
aria-valuenow
. - תוכן הטקסט הפנימי של
<progress>
.
const setProgress = () => {
zone.setAttribute('aria-busy', state.val < 1)
if (state.val === null) {
progress.removeAttribute('aria-valuenow')
progress.removeAttribute('value')
progress.focus()
return
}
const val = roundDecimals(state.val, 2)
const valPercent = val * 100 + "%"
progress.value = val
progress.setAttribute('aria-valuenow', valPercent)
progress.innerText = valPercent
}
העברת המיקוד להתקדמות
אחרי שהערכים מתעדכנים, משתמשים עם ראייה יראו את השינוי בהתקדמות, אבל משתמשים בתוכנות להקראת מסך עדיין לא יקבלו הודעה על השינוי. מתמקדים ברכיב <progress>
והדפדפן יכריז על העדכון.
const setProgress = () => {
zone.setAttribute('aria-busy', state.val < 1)
if (state.val === null) {
progress.removeAttribute('aria-valuenow')
progress.removeAttribute('value')
progress.focus()
return
}
const val = roundDecimals(state.val, 2)
const valPercent = val * 100 + "%"
progress.value = val
progress.setAttribute('aria-valuenow', valPercent)
progress.innerText = valPercent
progress.focus()
}
סיכום
עכשיו כשאתה יודע איך עשיתי את זה, איך היית עושה את זה‽ 🙂
יש כמה שינויים שהייתי רוצה לעשות אם הייתה לי עוד הזדמנות. אני חושב שיש מקום לנקות את הרכיב הנוכחי, ויש מקום לנסות לבנות רכיב בלי מגבלות הסגנון של פסאודו-המחלקות של רכיב <progress>
. כדאי לנסות!
כדאי לגוון את הגישות שלנו וללמוד את כל הדרכים לבנות אתרים.
אפשר ליצור סרטון הדגמה, לצייץ לי קישורים, ואוסיף אותו לקטע של רמיקסים מהקהילה שבהמשך!