نظرة عامة أساسية حول كيفية إنشاء شريط تحميل قابل للتكيّف مع الألوان وسهل الاستخدام باستخدام العنصر <progress>
في هذه المشاركة، أريد أن أشارك أفكاري حول كيفية إنشاء شريط تحميل قابل للتكيّف مع الألوان وسهل الاستخدام باستخدام العنصر <progress>
. جرِّب الإصدار التمهيدي واطّلِع على ملف المصدر.
إذا كنت تفضّل مشاهدة الفيديوهات، يمكنك الاطّلاع على نسخة من هذه المشاركة على YouTube:
نظرة عامة
يقدّم العنصر
<progress>
ملاحظات مرئية ومسموعة للمستخدمين حول اكتمال الإجراء. هذه الملاحظات المرئية مفيدة في سيناريوهات مثل: مستوى التقدّم في ملء نموذج،
عرض معلومات حول تنزيل أو تحميل ملف، أو حتى الإشارة إلى أنّه لا يمكن تحديد مستوى التقدّم ولكنّ العمل لا يزال نشطًا.
تم حلّ تحدّي واجهة المستخدم هذا باستخدام
عنصر 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
العناصر التالية في حال تفعيل الإعداد:
- انقر بزر الماوس الأيمن على صفحتك واختَر فحص العنصر لعرض "أدوات مطوّري البرامج".
- انقر على رمز ترس الإعدادات في أعلى يسار نافذة "أدوات المطوّر".
- ضمن العنوان العناصر، ابحث عن مربّع الاختيار عرض ظل وكيل المستخدم 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;
}
ستساعد السمات المخصّصة أيضًا في الحفاظ على اتّساق الرمز البرمجي، لأنّه لا يمكننا مرة أخرى تجميع هذه المحدّدات الخاصة بالمتصفّح معًا.
الإطارات الرئيسية
والهدف هو إنشاء صورة متحركة بلا نهاية تتكرر بشكل ذهابي وإيابي. سيتم ضبط اللقطات المفتاح
للبدء والانتهاء في 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:
- سمة
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>
. ننصحك باستكشافها.
لننوّع أساليبنا ونتعرّف على جميع الطرق لإنشاء تطبيقات على الويب.
أنشئ عرضًا توضيحيًا وأرسِل إلينا رابطًا على Twitter، وسنضيفه إلى قسم الريمكسات التي أنشأها المستخدمون أدناه.