ساختن مؤلفه نوار بارگیری

نمای کلی پایه‌ای از نحوه ساختن نوار بارگیری دسترس‌پذیر و تطبیق‌پذیر با رنگ با عنصر <progress>.

در این پست می‌خواهم ایده‌هایی را درباره نحوه ساختن نوار بارگیری دسترس‌پذیر و سازگار با رنگ با عنصر <progress> هم‌رسانی کنم. نسخه نمایشی را امتحان کنید و منبع را مشاهده کنید!

روشن و تاریک، نامشخص، افزایشی، و تکمیل‌شده در Chrome نمایش داده شده است.

اگر ویدیو را ترجیح می‌دهید، نسخه YouTube این پست را در اینجا ببینید:

نمای کلی

عنصر <progress> بازخورد دیداری و شنیداری درباره تکمیل به کاربران ارائه می‌دهد. این بازخورد دیداری برای سناریوهایی مانند پیشرفت در فرم، نمایش اطلاعات بارگیری یا بارگذاری، یا حتی نشان دادن اینکه مقدار پیشرفت نامشخص است اما کار همچنان فعال است، ارزشمند است.

این چالش GUI با عنصر HTML <progress> موجود کار کرد تا در دسترس‌پذیری صرفه‌جویی شود. رنگ‌ها و چیدمان‌ها محدودیت‌های سفارشی‌سازی را برای عنصر داخلی جابه‌جا می‌کنند تا مؤلفه را مدرن کنند و آن را برای سیستم‌های طراحی مناسب‌تر کنند.

برگه‌های روشن و تیره در هر مرورگر که نمای کلی نماد تطبیق‌پذیر را از بالا به پایین ارائه می‌دهد: Safari،‏ Firefox،‏ Chrome.
نمونه نمایشی در Firefox،‏ Safari،‏ iOS Safari،‏ Chrome، و Android Chrome در طرح‌های روشن و تیره نشان داده شده است.

نشانه‌گذاری

من تصمیم گرفتم عنصر <progress> را در یک <label> قرار دهم تا بتوانم از ویژگی‌های رابطه صریح به نفع یک رابطه ضمنی صرف‌نظر کنم. همچنین عنصر والدی را که تحت‌تأثیر وضعیت بار کردن قرار گرفته است برچسب‌گذاری کرده‌ام تا فناوری‌های صفحه‌خوان بتوانند آن اطلاعات را به کاربر منتقل کنند.

<progress></progress>

اگر value وجود نداشته باشد، پیشرفت عنصر نامشخص است. شاخصه max به‌طور پیش‌فرض ۱ است، بنابراین پیشرفت بین ۰ و ۱ است. برای مثال، تنظیم max روی ۱۰۰، محدوده را روی ۰-۱۰۰ تنظیم می‌کند. من تصمیم گرفتم در محدوده ۰ و ۱ بمانم و مقادیر پیشرفت را به ۰٫۵ یا ۵۰٪ ترجمه کنم.

پیشرفت برچسب‌دار

در رابطه ضمنی، عنصر پیشرفت با برچسبی مانند این احاطه می‌شود:

<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;
}

نماگرفت devtools که عنصر فقط آماده صفحه‌نمایش را نشان می‌دهد.

منطقه تحت‌تأثیر پیشرفت بارگیری

اگر بینایی سالمی داشته باشید، به‌راحتی می‌توانید نشانگر پیشرفت را با عناصر و بخش‌های مرتبط صفحه مرتبط کنید، اما برای کاربران کم‌بینا این موضوع چندان واضح نیست. با اختصاص دادن مشخصه aria-busy به بالاترین عنصر که پس‌از تکمیل بار کردن تغییر می‌کند، این مورد را بهبود دهید. علاوه‌براین، بااستفاده از aria-describedby رابطه بین پیشرفت و منطقه بارگیری را نشان دهید.

<main id="loading-zone" aria-busy="true">
  …
  <progress aria-describedby="loading-zone"></progress>
</main>

از جاوا اسکریپت، 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" استفاده کنید تا عنصر پیشرفت از جاوا اسکریپت قابل‌تمرکز شود. این موضوع برای فناوری صفحه‌خوان مهم است، زیرا با تغییر پیشرفت، تمرکز پیشرفت را فراهم می‌کند و به کاربر اعلام می‌کند که پیشرفت به‌روزشده تا کجا رسیده است.

سبک‌ها

عنصر پیشرفت هنگام سبک‌بندی کمی پیچیده است. عناصر 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 است). همچنین درصورت نیاز، به‌راحتی می‌توان آن را بزرگ‌تر کرد: فقط کافی است ۳ را به ۴ تغییر دهید، سپس 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 این عناصر را به شما نشان خواهد داد:

  1. روی صفحه‌تان کلیک راست کنید و بازرسی عنصر را انتخاب کنید تا DevTools باز شود.
  2. روی چرخ‌دنده «تنظیمات» در گوشه بالا سمت چپ پنجره DevTools کلیک کنید.
  3. در زیر عنوان عناصر، چارگوش انتخاب نمایش DOM سایه نماینده کاربر را پیدا و فعال کنید.

نماگرفت از جایی در DevTools که در آن می‌توان نمایش 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 تنظیم کرده است، درحالی‌که iOS Safari شیار آبی روشن دارد. در حالت تاریک هم همین‌طور است: Firefox نوار تیره دارد اما رنگ سفارشی‌ای را که تنظیم کرده‌ایم ندارد، و در مرورگرهای مبتنی بر Webkit کار می‌کند.

پویانمایی

هنگام کار با انتخابگرهای شبه داخلی مرورگر، اغلب با مجموعه محدودی از ویژگی‌های 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

جاوا اسکریپت نقش مهمی در عنصر <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
  }
}

رفع مشکلات ریاضی اعشاری جاوا اسکریپت

ازآنجایی‌که تصمیم گرفتم حداکثر پیش‌فرض پیشرفت را روی ۱ نگه دارم، توابع افزایش و کاهش نسخه نمایشی از ریاضیات اعشاری استفاده می‌کنند. جاوا اسکریپت و زبان‌های دیگر همیشه در این کار خوب نیستند. در اینجا تابع 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 Voice Over 
  که پیشرفت نوار بارگیری را برای کاربر می‌خواند.

نتیجه‌گیری

اکنون که می‌دانید من چطور این کار را کردم، شما چطور این کار را می‌کنید؟ 🙂

قطعاً اگر فرصت دیگری داشته باشم، چند تغییر ایجاد خواهم کرد. I think there's room to clean up the current component, and room to try and build one without the <progress> element's pseudo-class style limitations. ارزش کاوش کردن دارد!

بیایید رویکردهایمان را متنوع کنیم و همه روش‌های ساختن در وب را یاد بگیریم.

یک نسخه نمایشی بسازید، پیوندها را برای من توییت کنید، و من آن را به بخش بازآمیزی‌های انجمن در زیر اضافه خواهم کرد!

ریمیکس‌های انجمن