סקירה כללית בסיסית על יצירת סרגל טעינה מותאם לצבע ונגיש באמצעות האלמנט <progress>
.
בפוסט הזה אני רוצה לשתף את הדרך שבה אפשר ליצור סרגל טעינה מותאם לצבע ונגיש באמצעות הרכיב <progress>
. אפשר לנסות את הדמו ולעיין במקור.
אם אתם מעדיפים סרטון, הנה גרסה של הפוסט הזה ב-YouTube:
סקירה כללית
האלמנט <progress>
מספק למשתמשים משוב חזותי וקולי על השלמת המשימה. משוב חזותי כזה שימושי בתרחישים כמו: התקדמות בטופס, הצגת מידע על הורדה או העלאה, או אפילו הצגת הודעה על כך שסכום ההתקדמות לא ידוע אבל העבודה עדיין פעילה.
באתגר GUI הזה השתמשו באלמנט ה-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>
שכל דפדפן חושף. השימוש ברכיב ההתקדמות הוא באמצעות תג יחיד, אבל הוא מורכב מכמה אלמנטים צאצאים שנחשפים באמצעות פסאודו-סלקטורים של CSS. אם תפעילו את ההגדרה הזו, הרכיבים הבאים יוצגו ב-Chrome DevTools:
- לוחצים לחיצה ימנית על הדף ובוחרים באפשרות בדיקת המרכיב כדי להציג את כלי הפיתוח.
- לוחצים על גלגל השיניים של ההגדרות בפינה השמאלית העליונה של חלון DevTools.
- בקטע 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
מוצגים רק ברכיב <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;
}
נכסים מותאמים אישית גם יעזרו לשמור על הקוד ללא קוד מיותר, כי שוב, אי אפשר לקבץ יחד את הבוררים הספציפיים לדפדפן.
תמונות המפתח (keyframes)
המטרה היא ליצור אנימציה אינסופית שמתקדמת הלוך ושוב. נקודות ה-keyframe של ההתחלה והסיום יוגדרו ב-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>
ל-indeterminate.
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>
. כדאי לבדוק את האפשרויות האלה.
נרחיב את הגישות שלנו ונלמד את כל הדרכים לפיתוח באינטרנט.
אתם יכולים ליצור גרסת דמו, לשלוח לי קישורים בטוויטר ואוסיף אותה לקטע 'רמיקסים של הקהילה' שבהמשך.