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

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

لقطة شاشة للمكان الذي يمكن فيه تفعيل عرض 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 ومكان العثور على أجزاء عنصر التقدّم

لقطة شاشة لركن تصحيح الأخطاء حيث يظهر شريط التحميل يعمل في 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، وسنضيفه إلى قسم الريمكسات التي أنشأها المستخدمون أدناه.

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