בניית רכיב של סרגל טעינה

סקירה כללית בסיסית של תהליך היצירה של סרגל טעינה נגיש וגמיש לצבעים עם הרכיב <progress>.

בפוסט הזה אני רוצה לשתף אתכם בחשיבה על האופן שבו ניתן ליצור אפקט צבעוני סרגל טעינה נגיש עם הרכיב <progress>. רוצים לנסות את ההדגמה ו source!

כהה בהיר, קבוע, גבוה וסופי בהדגמה (דמו) ב-Chrome.

אם אתם מעדיפים סרטון, הנה גרסה של YouTube לפוסט:

סקירה כללית

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

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

הכרטיסיות הבהירות והכהות בכל דפדפן מספקות 
    סקירה כללית של הסמל המותאם אישית מלמעלה למטה: 
    Safari, Firefox, Chrome.
הדגמה מוצגת ב-Firefox, Safari, iOS Safari, Chrome ו-Android Chrome בסכימות בהירות כהות.

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.

תוספות של מאפיין אריה

למרות שהתפקיד המשתמע של רכיב <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);
}

צילום מסך של סרגל הטעינה ב-100% שמוצג בו סימן וי בסוף.

צבע

הדפדפן כולל צבעים משלו לרכיב ההתקדמות, והוא ניתן להתאמה בהיר כהה עם נכס 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%);
  }
}

סגנונות מיקוד

מוקדם יותר נתנו לאלמנט אינדקס שלילי של Tab כדי שאפשר יהיה להשתמש בו באופן פרוגרמטי ממוקד. כדאי להשתמש :focus-visible עד כדאי להתאים אישית את המיקוד כדי לבחור בסגנון טבעת המיקוד החכמה יותר. כדי לעשות את זה, לחיצה על 'מיקוד' לא תציג את טבעת המיקוד, אך לחיצות על המקלדת כן יוצגו. סרטון ב-YouTube עוסק בנושא הזה לעומק שכדאי לבדוק.

progress:focus-visible {
  outline-color: var(--_progress);
  outline-offset: 5px;
}

צילום מסך של סרגל הטעינה עם טבעת מיקוד מסביבו. כל הצבעים תואמים.

סגנונות מותאמים אישית בין דפדפנים

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

  1. לוחצים לחיצה ימנית על הדף ובוחרים באפשרות בדיקת המרכיב כדי להציג את כלי הפיתוח.
  2. לוחצים על סמל גלגל השיניים של ההגדרות בפינה השמאלית העליונה של חלון כלי הפיתוח.
  3. מתחת לכותרת Elements, מאתרים את האפשרות הצגת הצל של סוכן משתמש ומפעילים אותה. DOM תיבת סימון.

צילום מסך שמראה איפה בכלי הפיתוח זמינים חשיפת ה-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 רק רכיב <progress>. המשמעות היא גם שאנחנו לא יכולים לבצע את גוון הטראק באופן ישיר.

/*  Firefox  */
progress[value]::-moz-progress-bar {
  background-color: var(--_progress);
}

צילום מסך של Firefox והמיקום של חלקי רכיב ההתקדמות.

צילום מסך של פינת ניפוי הבאגים שבה Safari, iOS Safari 
  סרגל הטעינה מוצג ב-Firefox, ב-Chrome וב-Chrome ב-Android.

לתשומת ליבך: ב-Firefox צבע הטראק מוגדר כ-accent-color וב-Safari ב-iOS יש טראק בצבע תכלת. אותו הדבר במצב כהה: ב-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>

הערך של <main> הרלוונטי תלוי בשאלה אם ההתקדמות הושלמה או לא צריך לעדכן את הרכיב aria-busy :

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:

  1. המאפיין value של הרכיב <progress>.
  2. המאפיין aria-valuenow.
  3. תוכן הטקסט הפנימי <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()
}

צילום מסך של אפליקציית הקריינות ב-Mac OS 
  קריאת ההתקדמות של סרגל הטעינה למשתמש.

סיכום

עכשיו, אחרי שהסברתי איך עשיתי את זה, איך היית? 🙂

אני בהחלט יש כמה שינויים שארצה לעשות אם תהיה לי הזדמנות נוספת. לדעתי יש מקום למחוק את הרכיב הנוכחי, ואפשר גם לנסות ליצור רכיב כזה בלי הגבלות הסגנון של הרכיב <progress>. כדאי להתנסות!

בואו לגוון את הגישות שלנו ונלמד את כל הדרכים לבניית אתרים באינטרנט.

אפשר ליצור הדגמה, לשלוח לי קישורים של שלחו לי ציוץ ואני אוסיף אותם לקטע 'רמיקסים' של הקהילה שבהמשך.

רמיקסים קהילתיים