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

تقدّم هذه الصفحة نظرة عامة أساسية حول طريقة إنشاء شريط تحميل متكيف اللون وسهل الاستخدام باستخدام العنصر <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;
}

لقطة شاشة لأداة تطوير البرامج تكشف عن العنصر المخصّص للظهور على الشاشة فقط

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

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

لقطة شاشة للمكان الذي يمكن فيه إظهار shadow DOM في &quot;أدوات مطوّري البرامج&quot; (DevTools).

نمطا 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 وSafari على iOS و Firefox وChrome وChrome على Android

يُرجى ملاحظة أنّ 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;
}

ستساعد السمات المخصّصة أيضًا في إبقاء الرمز البرمجي غير مكتمل، لأنّه لا يمكننا مرة أخرى تجميع أدوات الاختيار الخاصة بالمتصفِّح هذه معًا.

الإطارات الرئيسية

الهدف هو رسم متحرك غير محدود يتنقل ذهابًا وإيابًا. سيتم ضبط اللقطات المفتاح للبدء والانتهاء في 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()
}

لقطة شاشة لتطبيق Voice Over في نظام التشغيل Mac OS 
  يقرأ مستوى تقدّم شريط التحميل للمستخدم

الخاتمة

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

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

لننوّع أساليبنا ونتعرّف على جميع الطرق لإنشاء تطبيقات على الويب.

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

الريمكسات التي أنشأها المستخدمون