إنشاء مكوِّن شريط التحميل

نظرة عامة أساسية حول كيفية إنشاء شريط تحميل متوافق مع الألوان ويمكن الوصول إليه باستخدام العنصر <progress>.

في هذه المشاركة، أريد مشاركة أفكار حول كيفية إنشاء شريط تحميل متوافق مع الألوان ويمكن الوصول إليه باستخدام العنصر <progress>. جرِّب النسخة التجريبية واطّلِع على المصدر.

عرض توضيحي لمؤشر تقدّم في الوضعَين الفاتح والداكن، وبحالة غير محدّدة، وبحالة تقدّم، وبحالة اكتمال على Chrome

إذا كنت تفضّل مشاهدة فيديو، إليك نسخة من هذا المنشور على YouTube:

نظرة عامة

يوفّر العنصر <progress> ملاحظات مرئية ومسموعة للمستخدمين حول اكتمال الإجراء. هذه الملاحظات المرئية مفيدة في سيناريوهات مثل: التقدم من خلال نموذج، أو عرض معلومات التنزيل أو التحميل، أو حتى إظهار أن مقدار التقدم غير معروف ولكن العمل لا يزال نشطًا.

في تحدّي واجهة المستخدم الرسومية هذا، استخدمنا عنصر HTML‏ <progress> الحالي لتوفير بعض الجهد في تسهيل الاستخدام. تتجاوز الألوان والتنسيقات حدود التخصيص للعنصر المضمّن، وذلك لتحديث المكوّن وجعله يتناسب بشكل أفضل مع أنظمة التصميم.

علامات تبويب فاتحة وداكنة في كل متصفّح تقدّم نظرة عامة على الرمز التكيّفي من الأعلى إلى الأسفل: Safari وFirefox وChrome
يظهر العرض التوضيحي في Firefox وSafari وSafari على iOS وChrome وChrome على Android في المظهرَين الفاتح والداكن.

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

لقطة شاشة لأدوات مطوّري البرامج التي تكشف عن عنصر &quot;جاهز للشاشة فقط&quot;

المنطقة المتأثرة بتقدّم التحميل

إذا كان لديك رؤية صحية، فقد يكون من السهل ربط مؤشر التقدم بالعناصر ذات الصلة ومناطق الصفحة، ولكن بالنسبة للمستخدمين ذوي العجز البصري، فإن الأمر ليس واضحًا للغاية. يمكنك تحسين ذلك من خلال تخصيص السمة 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);
}

لقطة شاشة لشريط التحميل بنسبة %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%);
  }
}

أنماط التركيز

في وقت سابق، أعطينا العنصر فهرس علامة تبويب سالبًا حتى يمكن التركيز عليه برمجيًا. استخدِم :focus-visible لتخصيص التركيز من أجل تفعيل نمط حلقة التركيز الأكثر ذكاءً. بهذه الطريقة، لن تظهر حلقة التركيز عند النقر بالماوس والتركيز على عنصر، ولكنّها ستظهر عند النقر على لوحة المفاتيح. يتناول الفيديو على YouTube هذا الموضوع بمزيد من التفصيل ويستحق المراجعة.

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

لقطة شاشة لشريط التحميل مع حلقة تركيز حوله الألوان متطابقة.

الأنماط المخصّصة على مستوى المتصفّحات

يمكنك تخصيص الأنماط من خلال اختيار أجزاء عنصر <progress> التي يعرضها كل متصفّح. يتم استخدام عنصر التقدم كعلامة واحدة، ولكنه يتكون من بعض العناصر الفرعية التي يتم عرضها عبر محددات CSS الزائفة. ستعرض لك "أدوات مطوّري البرامج في Chrome" هذه العناصر إذا فعّلت الإعداد:

  1. انقر بزر الماوس الأيمن على صفحتك واختَر فحص العنصر لعرض "أدوات مطوّري البرامج".
  2. انقر على رمز ترس "الإعدادات" في أعلى يسار نافذة "أدوات مطوّري البرامج".
  3. ضمن العنوان Elements (العناصر)، ابحث عن مربّع الاختيار Show user agent shadow DOM (عرض نموذج DOM لظل وكيل المستخدم) وفعِّله.

لقطة شاشة لمكان تفعيل عرض DOM الظل لوكيل المستخدم في &quot;أدوات مطوري البرامج في Chrome&quot;.

نمطا 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 ومكان العثور على أجزاء عنصر مستوى التقدّم

لقطة شاشة لـ Debugging Corner حيث يظهر شريط التحميل يعمل في Safari وiOS Safari وFirefox وChrome وChrome على Android.

لاحظ أنّ متصفّح Firefox يتضمّن لون مسار من accent-color، بينما يتضمّن متصفّح Safari على نظام التشغيل iOS مسارًا باللون الأزرق الفاتح. الأمر نفسه في الوضع الداكن: يحتوي 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

تؤدي لغة 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):

  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 
  يقرأ تقدّم شريط التحميل للمستخدم

الخاتمة

بعد أن عرفت كيف فعلت ذلك، كيف ستفعل ذلك؟ 🙂

هناك بالتأكيد بعض التغييرات التي أود إجراؤها إذا أتيحت لي فرصة أخرى. أعتقد أنّ هناك مجالاً لتنظيف المكوّن الحالي، ومجالاً لمحاولة إنشاء مكوّن بدون قيود نمط الفئة الزائفة للعنصر <progress>. يستحق الاستكشاف!

لنتعرّف على الطرق المختلفة لإنشاء المواقع الإلكترونية.

أنشئوا نسخة تجريبية، وأرسلوا لي الروابط على Twitter، وسأضيفها إلى قسم "الريمكسات من المنتدى" أدناه.

ريمكسات من المنتدى