بناء أنظمة قوائم يمكن الوصول إليها

نشرت: 2022-03-10
ملخص سريع ↬ هناك العديد من الأنواع المختلفة من القوائم على الويب. إن إنشاء تجارب شاملة هو مسألة استخدام أنماط القائمة الصحيحة في الأماكن الصحيحة ، مع الترميز والسلوك الصحيحين.

ملاحظة المحرر : ظهر هذا المقال في الأصل على Inclusive Components. إذا كنت ترغب في معرفة المزيد حول المقالات الشاملة المماثلة ، فاتبعinclusicomps على Twitter أو اشترك في موجز RSS. من خلال دعم التصميم الشامل للمكونات على Patreon ، يمكنك المساعدة في جعله قاعدة البيانات الأكثر شمولاً لمكونات الواجهة القوية المتاحة.

التصنيف صعب. خذ سرطان البحر ، على سبيل المثال. السرطانات الناسك ، والخزف ، وسرطان حدوة الحصان ليست - من الناحية التصنيفية - سرطان البحر الحقيقي . لكن هذا لا يمنعنا من استخدام لاحقة "السلطعون". يصبح الأمر أكثر إرباكًا عندما تتطور السرطانات غير الصحيحة بمرور الوقت وبفضل عملية تسمى السرطانات لتشبه السرطانات الحقيقية عن كثب. هذا هو الحال مع سرطان البحر الملك ، الذي يعتقد أنه كان سرطانًا ناسكًا في الماضي. تخيل حجم قذائفهم!

في التصميم ، غالبًا ما نرتكب نفس الخطأ بإعطاء أشياء مختلفة نفس الاسم. تبدو متشابهة ، لكن المظاهر قد تكون خادعة. يمكن أن يكون لهذا تأثير مؤسف على وضوح مكتبة المكونات الخاصة بك. من حيث التضمين ، قد يقودك أيضًا إلى إعادة توظيف مكون غير لائق لغويًا وسلوكيًا. يتوقع المستخدمون شيئًا ما وسيحصلون على آخر.

يشير مصطلح "القائمة المنسدلة" إلى مثال تقليدي. الكثير من الأشياء "منسدلة" في الواجهات ، بما في ذلك مجموعة <option> s من عنصر <select> ، وقائمة الروابط التي تم الكشف عنها بواسطة JavaScript والتي تشكل قائمة التنقل الفرعية. نفس الاسم؛ أشياء مختلفة تمامًا. (يسمي بعض الناس هذه "عمليات الانسحاب" بالطبع ، لكن دعونا لا ندخل في ذلك).

غالبًا ما تسمى القوائم المنسدلة التي تشكل مجموعة من الخيارات "القوائم" ، وأريد التحدث عنها هنا. سنبتكر قائمة طعام حقيقية ، ولكن هناك الكثير مما يمكن قوله عن القوائم غير الصحيحة على طول الطريق.

المزيد بعد القفز! أكمل القراءة أدناه ↓

لنبدأ باختبار. هل مربع الروابط المعلق من شريط التنقل في الرسم التوضيحي هو قائمة؟

يشتمل شريط التنقل الذي يحتوي على رابط متجر ، أسفله على تعليق مجموعة من ثلاثة روابط أخرى لأزياء الكلاب ومكواة الوافل والأجرام السماوية السحرية على التوالي.
يشتمل شريط التنقل الذي يحتوي على رابط متجر ، أسفله على تعليق مجموعة من ثلاثة روابط أخرى لأزياء الكلاب ومكواة الوافل والأجرام السماوية السحرية على التوالي. (معاينة كبيرة)

الجواب لا ، ليست قائمة صحيحة.

إنها اصطلاح طويل الأمد أن مخططات التنقل تتكون من قوائم الروابط. تقضي الاتفاقية القديمة تقريبًا أنه يجب توفير التنقل الفرعي كقوائم متداخلة من الارتباطات. إذا كنت سأزيل CSS للمكون الموضح أعلاه ، يجب أن أرى شيئًا مشابهًا لما يلي ، باستثناء اللون الأزرق الملون وفي Times New Roman.

من الناحية الدلالية ، فإن قوائم الروابط المتداخلة صحيحة في هذا السياق. أنظمة الملاحة هي في الحقيقة جداول محتوى وهذه هي الطريقة التي يتم بها تنظيم جداول المحتوى. الشيء الوحيد الذي يجعلنا نفكر حقًا في "القائمة" هو تصميم القوائم المتداخلة والطريقة التي يتم الكشف عنها عند التمرير أو التركيز.

هذا هو المكان الذي يخطئ فيه البعض ويبدأ في إضافة دلالات WAI-ARIA: aria-haspopup="true" ، role="menu" ، role="menuitem" إلخ. هناك مكان لهذه ، كما سنغطي ، ولكن ليس هنا . فيما يلي سببان:

  1. قوائم ARIA غير مخصصة للتنقل ولكن لسلوك التطبيق. تخيل نظام القائمة لتطبيق سطح المكتب.
  2. يجب أن يكون ارتباط المستوى الأعلى قابلاً للاستخدام كرابط ، مما يعني أنه لا يتصرف مثل زر القائمة.

بخصوص (2): عند اجتياز منطقة ملاحة بقوائم فرعية ، يتوقع المرء أن تظهر كل قائمة فرعية عند تحريك مؤشر الماوس أو تركيز ارتباط "المستوى الأعلى" ("التسوق" في الرسم التوضيحي). يقوم هذا بكشف القائمة الفرعية ووضع الروابط الخاصة بها في ترتيب التركيز. مع القليل من المساعدة من JavaScript لالتقاط التركيز وطمس الأحداث لاستمرار ظهور القوائم الفرعية عند الحاجة ، يجب أن يكون أي شخص يستخدم لوحة المفاتيح قادرًا على التنقل عبر كل رابط في كل طبقة ، بدوره.

أزرار القائمة التي تأخذ aria-haspopup="true" لا تتصرف بهذا الشكل. يتم تنشيطها عند النقر وليس لها أي غرض آخر سوى الكشف عن قائمة سرية.

خصومات الكتاب الاليكترونى bieten wir
على اليسار: زر قائمة يسمى "قائمة" مع أيقونة سهم يشير لأسفل و aria-موسعة = حالة خاطئة. اليمين: نفس زر القائمة ولكن مع فتح القائمة. هذا الزر موجود بالصيغة الموسعة = الحالة الحقيقية. (معاينة كبيرة)

كما هو موضح في الصورة ، سواء كانت هذه القائمة مفتوحة أو مغلقة ، يجب توصيلها aria-expanded . يجب عليك فقط تغيير هذه الحالة عند النقر ، وليس التركيز. لا يتوقع المستخدمون عادةً تغييرًا صريحًا للحالة عند مجرد حدث تركيز. في نظام الملاحة لدينا ، لا تتغير الحالة حقًا ؛ إنها مجرد خدعة تصفيف. من الناحية السلوكية ، يمكننا التنقل عبر التنقل كما لو لم تحدث خداع إظهار / إخفاء.

مشكلة التنقل مع القوائم الفرعية

تعمل قوائم التنقل الفرعية (أو "القوائم المنسدلة" للبعض) بشكل جيد باستخدام الماوس أو عن طريق لوحة المفاتيح ، ولكنها ليست ساخنة جدًا عندما يتعلق الأمر باللمس. عندما تضغط على رابط "Shop" ذي المستوى الأعلى في مثالنا لأول مرة ، فإنك تخبره بفتح القائمة الفرعية واتباع الرابط.

هناك نوعان من الحلول الممكنة هنا:

  1. منع السلوك الافتراضي لارتباطات المستوى الأعلى ( e.preventDefault() ) والنص في دلالات وسلوك قائمة WAI-ARIA الكاملة.
  2. تأكد من أن كل صفحة وجهة ذات مستوى أعلى بها جدول محتويات كبديل للقائمة الفرعية.

(1) غير مُرضٍ لأنه ، كما أشرت سابقًا ، هذه الأنواع من الدلالات والسلوكيات غير متوقعة في هذا السياق ، حيث الروابط هي عناصر التحكم في الموضوع. لم يعد بإمكان مستخدمي Plus الانتقال إلى صفحة المستوى الأعلى ، إن وجدت.

Sidenote: ما هي الأجهزة التي تعمل باللمس؟

من المغري التفكير ، "هذا ليس حلاً رائعًا ، لكنني سأضيفه فقط لواجهات اللمس". المشكلة هي: كيف يكتشف المرء ما إذا كان الجهاز به شاشة تعمل باللمس؟

بالتأكيد لا يجب أن تساوي "الشاشة الصغيرة" مع "اللمس المنشط". بعد أن عملت في نفس المكتب حيث كان الناس يصنعون شاشات تعمل باللمس للمتاحف ، يمكنني أن أؤكد لكم أن بعض أكبر الشاشات الموجودة هي شاشات تعمل باللمس. لوحة المفاتيح المزدوجة وأجهزة الكمبيوتر المحمولة التي تعمل باللمس أصبحت غزيرة الإنتاج أيضًا.

على نفس المنوال ، فإن العديد من الأجهزة الصغيرة وليس جميعها هي أجهزة تعمل باللمس. في التصميم الشامل ، لا يمكنك وضع افتراضات.

يعتبر القرار (2) أكثر شمولية وقوة من حيث أنه يوفر "احتياطيًا" لمستخدمي جميع المدخلات. لكن اقتباسات التخويف حول المصطلح الاحتياطي هنا متعمدة تمامًا لأنني أعتقد في الواقع أن جداول المحتوى داخل الصفحة هي طريقة ممتازة لتوفير التنقل.

يبدو أن فريق الخدمات الرقمية الحكومية الحائز على جائزة يوافق. ربما تكون قد شاهدتها أيضًا على ويكيبيديا.

تكون جداول محتوى Gov.uk في حدها الأدنى مع واصلات كأنماط قائمة. توفر ويكيبيديا مربعًا رماديًا بحدود به عناصر مرقمة. كلاهما مصنفان محتويات.
تكون جداول محتوى Gov.uk في حدها الأدنى مع واصلات كأنماط قائمة. توفر ويكيبيديا مربعًا رماديًا بحدود به عناصر مرقمة. كلاهما مصنفان محتويات.

جداول المحتويات

جداول المحتوى عبارة عن تنقل للصفحات ذات الصلة أو أقسام الصفحة ويجب أن تكون مشابهة لغويًا لمناطق التنقل في الموقع الرئيسي ، باستخدام عنصر <nav> ، وقائمة ، وآلية تصنيف المجموعة.

 <nav aria-labelledby="sections-heading"> <h2>Products</h2> <ul> <li><a href="/products/dog-costumes">Dog costumes</a></li> <li><a href="/products/waffle-irons">Waffle irons</a></li> <li><a href="/products/magical-orbs">Magical orbs</a></li> </ul> </nav> <!-- each section, in order, here -->

ملاحظات

  • في هذا المثال ، نتخيل أن كل قسم هو صفحته الخاصة ، كما لو كان في القائمة الفرعية المنسدلة.
  • من المهم أن تحتوي كل صفحة من صفحات "التسوق" هذه على نفس الهيكل ، مع وجود جدول محتوى "المنتجات" في نفس المكان. الاتساق يدعم الفهم.
  • تقوم القائمة بتجميع العناصر وتعدادها في إخراج التكنولوجيا المساعدة ، مثل الصوت الاصطناعي لقارئ الشاشة.
  • يتم تسمية <nav> بشكل متكرر بالعنوان باستخدام aria-labelledby . هذا يعني أنه سيتم الإعلان عن "التنقل في المنتجات" في معظم برامج قراءة الشاشة عند دخول المنطقة عن طريق علامة التبويب . وهذا يعني أيضًا أن "التنقل في المنتجات" سيتم تحديده في واجهات عنصر قارئ الشاشة ، والتي يمكن للمستخدمين من خلالها الانتقال إلى المناطق مباشرةً.

كل ذلك في صفحة واحدة

إذا كان بإمكانك احتواء جميع الأقسام في صفحة واحدة دون أن تصبح طويلة جدًا وشاقة في التمرير ، فهذا أفضل. ما عليك سوى الارتباط بمعرف التجزئة لكل قسم. على سبيل المثال ، يجب أن يشير href="#waffle-irons" إلى .

 <nav aria-labelledby="sections-heading"> <h2>Products</h2> <ul> <li><a href="#dog-costumes">Dog costumes</a></li> <li><a href="#waffle-irons">Waffle irons</a></li> <li><a href="#magical-orbs">Magical orbs</a></li> </ul> </nav> <!-- dog costumes section here --> <section tabindex="-1"> <h2>Waffle Irons</h2> </section> <!-- magical orbs section here -->

( ملاحظة: بعض المتصفحات ضعيفة في إرسال التركيز فعليًا إلى أجزاء الصفحة المرتبطة. يؤدي وضع tabindex="-1" على الجزء الهدف إلى إصلاح هذا.)

عندما يحتوي الموقع على قدر كبير من المحتوى ، فإن بنية المعلومات التي تم إنشاؤها بعناية ، والتي يتم التعبير عنها من خلال الاستخدام الليبرالي لجداول "قوائم" المحتوى ، تكون مفضلة بلا حدود على نظام قائمة منسدلة غير مستقر وغير عملي. لا يقتصر الأمر على جعل الاستجابة أكثر سهولة ، ويتطلب القليل من التعليمات البرمجية للقيام بذلك ، ولكنه يجعل الأمور أكثر وضوحًا: حيث تخفي أنظمة القائمة المنسدلة الهيكل بعيدًا ، تجعل جداول المحتوى عارية.

تتضمن بعض المواقع ، بما في ذلك gov.uk التابع للخدمة الرقمية الحكومية ، صفحات فهرس (أو "موضوع") تمثل جداول محتوى فقط . إنه مفهوم قوي أن منشئ الموقع الثابت الشهير Hugo ينشئ مثل هذه الصفحات افتراضيًا.

مخطط نمط شجرة العائلة مع الصفحة المقصودة للموضوع في الأعلى مع فرعين منفردين للصفحة. يحتوي كل فرع من فروع الصفحة الفردية على عدة فروع لأقسام الصفحات
مخطط نمط شجرة العائلة مع الصفحة المقصودة للموضوع في الأعلى مع فرعين منفردين للصفحة. يحتوي كل فرع من فروع الصفحات الفردية على عدة فروع لأقسام الصفحات (معاينة كبيرة)

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

أزرار قائمة التنقل

بينما نحن في موضوع القوائم ذات الصلة بالملاحة المزيفة ، سأكون مقصرا ألا أتحدث عن أزرار قائمة التنقل. من شبه المؤكد أنك رأيت هذه الرموز تشير إلى ثلاثة أسطر "همبرغر" أو رمز "نافيكون".

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

زر التنقل هو أقرب شيء درسناه حتى الآن لزر القائمة الحقيقي . نظرًا لأن الغرض منه هو تبديل توفر قائمة عند النقر ، فيجب أن:

  1. يعرّف عن نفسه على أنه زر وليس رابط ؛
  2. حدد الحالة الموسعة أو المطوية لقائمتها المقابلة (والتي ، بعبارات صارمة ، مجرد قائمة من الروابط).

تحسين تدريجي

لكن دعونا لا نتقدم على أنفسنا. يجب أن نضع في اعتبارنا التحسين التدريجي وننظر في كيفية عمل ذلك بدون JavaScript.

في مستند HTML غير المحسن ، ليس هناك الكثير الذي يمكنك القيام به باستخدام الأزرار (باستثناء أزرار الإرسال ولكن هذا لا يرتبط ارتباطًا وثيقًا بما نريد تحقيقه هنا). بدلاً من ذلك ، ربما يجب أن نبدأ فقط برابط يأخذنا إلى التنقل؟

 <a href="#navigation">navigation</a> <!-- some content here perhaps --> <nav> <ul> <li><a href="/">Home</a></li> <li><a href="/about">About</a></li> <li><a href="/shop">Shop</a></li> <li><a href="/content">Content</a></li> </ul> </nav>

ليس هناك فائدة كبيرة لامتلاك الرابط ما لم يكن هناك الكثير من المحتوى بين الرابط والتنقل. نظرًا لأن التنقل في الموقع يجب أن يظهر دائمًا بالقرب من أعلى ترتيب المصدر ، فلا داعي لذلك. لذا ، في الحقيقة ، قائمة التنقل في حالة عدم وجود JavaScript يجب أن تكون ... بعض التنقل.

 <nav> <ul> <li><a href="/">Home</a></li> <li><a href="/about">About</a></li> <li><a href="/shop">Shop</a></li> <li><a href="/content">Content</a></li> </ul> </nav>

يمكنك تحسين ذلك عن طريق إضافة الزر ، في حالته الأولية ، وإخفاء التنقل (باستخدام السمة hidden ):

 <nav> <button aria-expanded="false">Menu</button> <ul hidden> <li><a href="/">Home</a></li> <li><a href="/about">About</a></li> <li><a href="/shop">Shop</a></li> <li><a href="/contact">Contact</a></li> </ul> </nav>

بعض المتصفحات القديمة - أنت تعرف أيها - لا تدعم hidden ، لذا تذكر أن تضع ما يلي في CSS الخاص بك. يعمل على إصلاح المشكلة لأن display: none له نفس التأثير لإخفاء القائمة من التقنيات المساعدة وإزالة الروابط من ترتيب التركيز.

 [hidden] { display: none; }

إن القيام بأفضل ما يمكن لدعم البرامج القديمة هو ، بالطبع ، فعل تصميم شامل. البعض غير قادر أو غير راغب في الترقية.

تحديد مستوى

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

 <nav> </nav>

إليك كيفية تبديل الحالة:

 var navButton = document.querySelector('nav button'); navButton.addEventListener('click', function() { let expanded = this.getAttribute('aria-expanded') === 'true' || false; this.setAttribute('aria-expanded', !expanded); let menu = this.nextElementSibling; menu.hidden = !menu.hidden; });

ضوابط الأغنية

كما كتبت في Aria-controls Is Poop ، فإن سمة عناصر التحكم في aria-controls ، التي تهدف إلى مساعدة مستخدمي قارئ الشاشة على التنقل من عنصر تحكم إلى عنصر محكوم ، مدعومة فقط في قارئ الشاشة JAWS. لذلك لا يمكنك الاعتماد عليه ببساطة.

بدون طريقة جيدة لتوجيه المستخدمين بين العناصر ، يجب عليك بدلاً من ذلك التأكد من صحة أحد العناصر التالية:

  1. يكون الارتباط الأول للقائمة الموسعة هو التالي بترتيب التركيز بعد الزر (كما في مثال الرمز السابق).
  2. يركز الارتباط الأول برمجيًا على الكشف عن القائمة.

في هذه الحالة ، أوصي بـ (1). إنه أبسط كثيرًا نظرًا لأنه لا داعي للقلق بشأن إعادة التركيز إلى الزر وعلى الحدث (الأحداث) للقيام بذلك. أيضًا ، لا يوجد حاليًا أي شيء لتحذير المستخدمين من أنه سيتم نقل تركيزهم إلى مكان مختلف. في القوائم الحقيقية التي سنناقشها قريبًا ، هذه هي وظيفة aria-haspopup="true" .

إن استخدام aria-controls لا يضر كثيرًا ، باستثناء أنه يجعل القراءة في برامج قراءة الشاشة أكثر تفصيلاً. ومع ذلك ، قد يتوقع بعض مستخدمي JAWS ذلك. إليك كيفية تطبيقه ، باستخدام id القائمة باعتباره التشفير:

 <nav> <button aria-expanded="false" aria-controls="menu-list">Menu</button> <ul hidden> <li><a href="/">Home</a></li> <li><a href="/about">About</a></li> <li><a href="/shop">Shop</a></li> <li><a href="/contact">Contact</a></li> </ul> </nav>

أدوار القائمة والقائمة

يجب أن تُعرّف القائمة الحقيقية (بمعنى WAI-ARIA) نفسها على هذا النحو باستخدام دور menu (للحاوية) ، وعادةً ، عناصر القائمة الفرعية (قد يتم تطبيق أدوار menuitem أخرى). تعمل أدوار الوالدين والطفل هذه معًا لتوفير معلومات للتقنيات المساعدة. إليك كيفية زيادة القائمة بحيث تحتوي على دلالات قائمة:

 <ul role="menu"> <li role="menuitem">Item 1</li> <li role="menuitem">Item 2</li> <li role="menuitem">Item 3</li> </ul>

نظرًا لأن قائمة التنقل الخاصة بنا بدأت تتصرف إلى حد ما مثل قائمة "حقيقية" ، ألا يجب أن تكون موجودة؟

الجواب القصير هو لا. الإجابة الطويلة هي: لا ، لأن عناصر قائمتنا تحتوي على menuitem وعناصر قائمة لا يُقصد بها أن يكون لها أحفاد تفاعلية. أي أنها عناصر التحكم في القائمة.

يمكننا بالطبع منع دلالات القائمة الخاصة بـ <li> s using role="presentation" أو role="none" (وهي مكافئة) ووضع دور menuitem على كل رابط. ومع ذلك ، سيؤدي هذا إلى إلغاء دور الارتباط الضمني. بمعنى آخر ، سيتم الإعلان عن المثال الذي سيتم اتباعه على أنه "الصفحة الرئيسية ، عنصر القائمة" ، وليس "الصفحة الرئيسية ، أو الرابط" أو "الصفحة الرئيسية ، عنصر القائمة ، الرابط". تتجاوز أدوار ARIA أدوار HTML ببساطة.

 <!-- will be read as "Home, menu item" --> <li role="presentation"> <a href="/" role="menuitem">Home</a> </li>

نريد أن يعرف المستخدم أنه يستخدم رابطًا ويمكنه توقع سلوك الارتباط ، لذلك هذا ليس جيدًا. كما قلت ، القوائم الحقيقية مخصصة لسلوك التطبيق (الذي يحركه JavaScript).

ما تبقى لدينا هو نوع من المكونات المختلطة ، وهي ليست قائمة حقيقية تمامًا ولكنها تخبر المستخدمين على الأقل ما إذا كانت قائمة الروابط مفتوحة ، وذلك بفضل الحالة aria-expanded . هذا هو النمط المُرضي تمامًا لقوائم التنقل.

Sidenote: عنصر <select>

إذا كنت مشتركًا في تصميم سريع الاستجابة من البداية ، فقد تتذكر نمطًا تم فيه تكثيف التنقل في عنصر <select> لإطارات العرض الضيقة.

الهاتف مع تحديد عنصر يظهر "المنزل" المحدد في أعلى منفذ العرض
سماعة مع عنصر محدد يظهر تحديد "المنزل" أعلى منفذ العرض.

كما هو الحال مع أزرار التبديل القائمة على خانة الاختيار التي ناقشناها ، فإن استخدام عنصر أصلي يتصرف إلى حد ما على النحو المنشود دون برمجة نصية إضافية يعد اختيارًا جيدًا للكفاءة - وخاصة على الأجهزة المحمولة - الأداء. وعناصر <select> هي قوائم من نوع ما ، لها دلالات مشابهة لقائمة تشغيل الأزرار التي سننشئها قريبًا.

ومع ذلك ، تمامًا كما هو الحال مع زر تبديل مربع الاختيار ، فإننا نستخدم عنصرًا مرتبطًا بإدخال المدخلات ، وليس مجرد الاختيار. من المحتمل أن يتسبب هذا في إرباك العديد من المستخدمين - خاصة وأن هذا النمط يستخدم JavaScript لجعل <option> المحدد يتصرف مثل الرابط. يعتبر التغيير غير المتوقع للسياق الذي أحدثه هذا فشلًا وفقًا لمعيار WCAG 3.2.2 عند الإدخال (المستوى A).

القوائم الحقيقية

الآن وقد أجرينا مناقشة حول القوائم الزائفة وشبه القوائم ، فقد حان الوقت لإنشاء قائمة حقيقية ، كما يتم فتحها وإغلاقها بواسطة زر قائمة صحيح. من هنا فصاعدًا سأشير إلى الزر والقائمة معًا على أنهما مجرد "زر قائمة".

ولكن في أي جوانب سيكون زر القائمة لدينا صحيحًا؟ حسنًا ، سيكون مكون قائمة مخصصًا لاختيار الخيارات في تطبيق الموضوع ، والذي ينفذ جميع الدلالات المتوقعة والسلوكيات المقابلة ليتم اعتبارها تقليدية لمثل هذه الأداة.

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

في هذا المثال ، سنتخيل أن تطبيقنا هو نوع من اللعبة أو الاختبار. سيسمح زر القائمة الخاص بنا للمستخدم باختيار مستوى الصعوبة. مع وجود جميع الدلالات في مكانها الصحيح ، تبدو القائمة كما يلي:

 <button aria-haspopup="true" aria-expanded="false"> Difficulty <span aria-hidden="true">&#x25be;</span> </button> <div role="menu"> <button role="menuitem">Easy</button> <button role="menuitem">Medium</button> <button role="menuitem">Incredibly Hard</button> </div>

ملاحظات

  • تشير خاصية aria-haspopup ببساطة إلى أن الزر يفرز قائمة. إنه بمثابة تحذير من أنه عند الضغط عليه ، سيتم نقل المستخدم إلى القائمة "المنبثقة" (سنغطي سلوك التركيز قريبًا). قيمته لا تتغير - فهي تظل true في جميع الأوقات.
  • يحتوي <span> الموجود داخل الزر على نقطة unicode لمثلث صغير أسود يشير لأسفل. تشير هذه الاتفاقية بصريًا إلى ما تفعله aria-haspopup غير المرئي - أن الضغط على الزر سيكشف شيئًا تحته. تمنع الإحالة aria-hidden="true" قراءة الشاشة من الإعلان عن "مثلث يشير لأسفل" أو ما شابه. بفضل aria-haspopup ، ليست هناك حاجة إليها في السياق غير المرئي.
  • تكتمل خاصية aria-haspopup aria-expanded . يخبر هذا المستخدم ما إذا كانت القائمة حاليًا في حالة مفتوحة (موسعة) أو مغلقة (مطوية) من خلال التبديل بين القيم true false .
  • تأخذ القائمة نفسها دور menu (المسماة بشكل مناسب). يستغرق الأمر أحفاد مع دور menuitem . لا يحتاجون إلى أن يكونوا أطفالًا مباشرين لعنصر menu ، لكنهم في هذه الحالة - من أجل البساطة.

سلوك لوحة المفاتيح والتركيز

عندما يتعلق الأمر بجعل الوصول إلى لوحة مفاتيح عناصر التحكم التفاعلية ، فإن أفضل ما يمكنك فعله هو استخدام العناصر الصحيحة. نظرًا لأننا نستخدم عناصر <button> هنا ، فيمكننا التأكد من أن أحداث النقر ستنطلق عند ضغطات مفاتيح Enter و Space ، كما هو محدد في واجهة HTMLButtonElement. هذا يعني أيضًا أنه يمكننا تعطيل عناصر القائمة باستخدام خاصية disabled الأزرار المرتبطة.

ومع ذلك ، هناك الكثير لتفاعل لوحة المفاتيح على زر القائمة. فيما يلي ملخص لكل التركيز وسلوك لوحة المفاتيح التي سنقوم بتنفيذها ، بناءً على ممارسات التأليف WAI-ARIA 1.1:

أدخل أو مسافة أو على زر القائمة يفتح القائمة
على عنصر القائمة ينقل التركيز إلى عنصر القائمة التالي ، أو عنصر القائمة الأول إذا كنت على العنصر الأخير
على عنصر القائمة ينقل التركيز إلى عنصر القائمة السابق ، أو عنصر القائمة الأخير إذا كنت تستخدم العنصر الأول
على زر القائمة يغلق القائمة إذا كانت مفتوحة
Esc في عنصر القائمة يغلق القائمة ويركز زر القائمة

تتمثل ميزة نقل التركيز بين عناصر القائمة باستخدام مفاتيح الأسهم في الاحتفاظ بعلامة التبويب للانتقال من القائمة. من الناحية العملية ، هذا يعني أن المستخدمين ليسوا مضطرين للتنقل بين كل عنصر من عناصر القائمة للخروج من القائمة - وهو تحسن كبير في سهولة الاستخدام ، لا سيما عندما يكون هناك العديد من عناصر القائمة.

تطبيق tabindex="-1" يجعل عناصر القائمة غير قابلة للتركيز بواسطة Tab ولكنه يحافظ على القدرة على تركيز العناصر برمجيًا ، عند التقاط ضغطات المفاتيح على مفاتيح الأسهم.

 <button aria-haspopup="true" aria-expanded="false"> Difficulty <span aria-hidden="true">&#x25be;</span> </button> <div role="menu"> <button role="menuitem" tabindex="-1">Easy</button> <button role="menuitem" tabindex="-1">Medium</button> <button role="menuitem" tabindex="-1">Incredibly Hard</button> </div>

الطريقة المفتوحة

كجزء من تصميم API سليم ، يمكننا إنشاء طرق للتعامل مع الأحداث المختلفة.

على سبيل المثال ، يحتاج الأسلوب open إلى تبديل قيمة aria-expanded إلى "true" ، وتغيير الخاصية المخفية للقائمة إلى false ، والتركيز على القائمة menuitem في القائمة التي لم يتم تعطيلها:

 MenuButton.prototype.open = function () { this.button.setAttribute('aria-expanded', true); this.menu.hidden = false; this.menu.querySelector(':not(\[disabled])').focus(); return this; }

يمكننا تنفيذ هذه الطريقة حيث يضغط المستخدم على مفتاح لأسفل في مثيل زر قائمة مركزة:

 this.button.addEventListener('keydown', function (e) { if (e.keyCode === 40) { this.open(); } }.bind(this));

بالإضافة إلى ذلك ، سيتمكن المطور الذي يستخدم هذا النص البرمجي الآن من فتح القائمة برمجيًا:

 exampleMenuButton = new MenuButton(document.querySelector('\[aria-haspopup]')); exampleMenuButton.open();

Sidenote: اختراق مربع الاختيار

بقدر الإمكان ، من الأفضل عدم استخدام JavaScript إلا إذا احتجت إلى ذلك. يعد استخدام تقنية ثالثة فوق HTML و CSS بالضرورة زيادة في التعقيد والهشاشة النظامية. ومع ذلك ، لا يمكن بناء جميع المكونات بشكل مرضٍ بدون وجود JavaScript في المزيج.

في حالة أزرار القائمة ، أدى الحماس لجعلها "تعمل بدون جافا سكريبت" إلى شيء يسمى اختراق مربع الاختيار. هذا هو المكان الذي يتم فيه استخدام الحالة المحددة (أو غير المحددة) لمربع الاختيار المخفي لتبديل رؤية عنصر القائمة باستخدام CSS.

 /* menu closed */ [type="checkbox"] + [role="menu"] { display: none; } /* menu open */ [type="checkbox"]:checked + [role="menu"] { display: block; }

بالنسبة لمستخدمي قارئ الشاشة ، فإن دور مربع الاختيار والحالة المحددة غير منطقيين في هذا السياق. يمكن التغلب على هذا جزئيًا عن طريق إضافة role="button" إلى مربع الاختيار.

 <input type="checkbox" role="button" aria-haspopup="true">

لسوء الحظ ، يؤدي هذا إلى منع اتصال الحالة الضمني الذي تم التحقق منه ، مما يحرمنا من تعليقات الحالة الخالية من JavaScript (على الرغم من أنها كانت "ضعيفة" في هذا السياق).

ولكن من الممكن أن تنتحل aria-expanded . نحتاج فقط إلى تزويد الملصق الخاص بنا بامتدادين على النحو التالي.

 <input type="checkbox" role="button" aria-haspopup="true" class="vh"> <label for="toggle" data-opens-menu> Difficulty <span class="vh expanded-text">expanded&lt;/span> <span class="vh collapsed-text">collapsed</span> <span aria-hidden="true">&#x25be;</span> </label>

كلاهما مخفي بصريًا باستخدام فئة visually-hidden ، ولكن - اعتمادًا على الحالة التي نحن فيها - يتم إخفاء واحد فقط لقارئات الشاشة أيضًا. أي أن display: none ، ويتم تحديد ذلك من خلال حالة التحقق الموجودة (ولكن لم يتم الإبلاغ عنها):

 /* class to hide spans visually */ .vh { position: absolute !important; clip: rect(1px, 1px, 1px, 1px); padding: 0 !important; border: 0 !important; height: 1px !important; width: 1px !important; overflow: hidden; } /* reveal the correct state wording to screen readers based on state */ [type="checkbox"]:checked + label .expanded-text { display: inline; } [type="checkbox"]:checked + label .collapsed-text { display: none; } [type="checkbox"]:not(:checked) + label .expanded-text { display: none; } [type="checkbox"]:not(:checked) + label .collapsed-text { display: inline; }

هذا ذكي وكل شيء ، لكن زر القائمة الخاص بنا لا يزال غير مكتمل لأن سلوكيات التركيز المتوقعة التي ناقشناها ببساطة لا يمكن تنفيذها بدون JavaScript.

هذه السلوكيات تقليدية ومتوقعة ، مما يجعل الزر أكثر قابلية للاستخدام. ومع ذلك ، إذا كنت حقًا بحاجة إلى تنفيذ زر قائمة بدون JavaScript ، فهذا أقرب ما يمكن. بالنظر إلى زر قائمة التنقل المختصرة الذي قمت بتغطيته سابقًا ، فإنه يقدم محتوى قائمة لا يعتمد على JavaScript نفسها (أي الروابط) ، قد يكون هذا الأسلوب خيارًا مناسبًا.

من أجل المتعة ، إليك رمز codePen يقوم بتنفيذ زر قائمة تنقل خالٍ من JavaScript.

انظر مثال زر قائمة Pen Navigation no JS by Heydon (heydon) على CodePen.

( ملاحظة: Only Space يفتح القائمة.)

حدث "اختر"

يجب أن يؤدي تنفيذ بعض الأساليب إلى إصدار أحداث حتى نتمكن من إعداد مستمعين. على سبيل المثال ، يمكننا إرسال حدث choose عندما ينقر المستخدم على عنصر قائمة. يمكننا إعداد هذا باستخدام CustomEvent ، والذي يتيح لنا تمرير وسيطة إلى خاصية detail الحدث. في هذه الحالة ، ستكون الوسيطة ("الاختيار") هي عقدة DOM لعنصر القائمة المختار.

 MenuButton.prototype.choose = function (choice) { // Define the 'choose' event var chooseEvent = new CustomEvent('choose', { detail: { choice: choice } }); // Dispatch the event this.button.dispatchEvent(chooseEvent); return this; }

هناك كل أنواع الأشياء التي يمكننا القيام بها بهذه الآلية. ربما لدينا منطقة حية تم إعدادها id menuFeedback :

 <div role="alert"></div>

الآن يمكننا إعداد مستمع وملء المنطقة الحية بالمعلومات المخفية داخل الحدث:

 exampleMenuButton.addEventListener('choose', function (e) { // Get the node's text content (label) var choiceLabel = e.details.choice.textContent; // Get the live region node var liveRegion = document.getElementById('menuFeedback'); // Populate the live region liveRegion.textContent = 'Your difficulty level is ${choiceLabel}'; });
عندما يختار المستخدم أحد الخيارات ، يتم إغلاق القائمة ويعود التركيز إلى زر القائمة. من المهم إعادة المستخدمين إلى عنصر التشغيل بعد إغلاق القائمة.
عندما يختار المستخدم أحد الخيارات ، يتم إغلاق القائمة ويعود التركيز إلى زر القائمة. من المهم أن يتم إرجاع المستخدمين إلى عنصر التشغيل بعد إغلاق القائمة. (معاينة كبيرة)

عند تحديد عنصر قائمة ، سيسمع مستخدم قارئ الشاشة ، "لقد اخترت [تسمية عنصر القائمة]" . تعلن منطقة حية (محددة هنا بالإحالة role=“alert” ) عن محتواها في برامج قراءة الشاشة كلما تغير هذا المحتوى. المنطقة الحية ليست إلزامية ، لكنها مثال على ما قد يحدث في الواجهة كرد فعل على قيام المستخدم باختيار القائمة.

الخيارات المستمرة

ليست كل عناصر القائمة مخصصة لاختيار الإعدادات الثابتة. يتصرف الكثير منها مثل الأزرار القياسية التي تجعل شيئًا ما في الواجهة يحدث عند الضغط عليه. ومع ذلك ، في حالة زر قائمة الصعوبة لدينا ، نود الإشارة إلى إعداد الصعوبة الحالي - الإعداد الذي تم اختياره أخيرًا.

تعمل السمة aria-checked="true" للعناصر التي ، بدلاً من menuitem ، تأخذ دور menuitemradio . يبدو الترميز المحسّن ، مع تحديد العنصر الثاني ( مجموعة ) كما يلي:

 <button aria-haspopup="true" aria-expanded="false"> Difficulty <span aria-hidden="true">&#x25be;</span> </button> <div role="menu"> <button role="menuitemradio" tabindex="-1">Easy</button> <button role="menuitemradio" aria-checked="true" tabindex="-1">Medium</button> <button role="menuitemradio" tabindex="-1">Incredibly Hard</button> </div>

تشير القوائم الأصلية في العديد من الأنظمة الأساسية إلى العناصر المختارة باستخدام علامات الاختيار. يمكننا القيام بذلك دون مشاكل باستخدام القليل من CSS:

 [role="menuitem"] [aria-checked="true"]::before { content: '\2713\0020'; }

أثناء عبور القائمة مع تشغيل قارئ الشاشة ، سيؤدي التركيز على هذا العنصر المحدد إلى ظهور إعلان مثل "علامة اختيار ، عنصر قائمة متوسط ​​، محدد" .

يختلف السلوك عند فتح قائمة بقائمة محددة menuitemradio اختلافًا طفيفًا. بدلاً من تركيز العنصر الأول (الممكّن) في القائمة ، يتم التركيز على العنصر المحدد بدلاً من ذلك.

يبدأ زر القائمة مع عدم فتح القائمة. عند فتح الإعداد الثاني (متوسط) الصعوبة ، يتم التركيز. يكون مسبوقًا بعلامة اختيار بناءً على وجود السمة التي تم فحصها من خلال aria.
يبدأ زر القائمة مع عدم فتح القائمة. عند فتح الإعداد الثاني (متوسط) الصعوبة ، يتم التركيز. يكون مسبوقًا بعلامة اختيار بناءً على وجود السمة التي تم فحصها من خلال aria. (معاينة كبيرة)

ما فائدة هذا السلوك؟ يتم تذكير المستخدم (أي مستخدم) بالخيار المحدد مسبقًا. في القوائم التي تحتوي على العديد من الخيارات المتزايدة (على سبيل المثال ، مجموعة من مستويات التكبير / التصغير) ، يتم وضع الأشخاص الذين يعملون عن طريق لوحة المفاتيح في الوضع الأمثل لإجراء الضبط.

استخدام زر القائمة مع قارئ الشاشة

في هذا الفيديو ، سأوضح لك كيف يبدو استخدام زر القائمة مع قارئ الشاشة Voiceover و Chrome. يستخدم المثال عناصر بها menuitemradio و aria-checked وسلوك التركيز الذي تمت مناقشته. يمكن توقع تجارب مماثلة عبر سلسلة برامج قارئ الشاشة الشائعة.

زر قائمة شاملة على جيثب

لقد عملت مع Kitty Giraudel معًا على إنشاء مكون زر قائمة مع ميزات API التي وصفتها ، والمزيد. يجب أن تشكر Hugo على العديد من هذه الميزات ، نظرًا لأنها كانت تستند إلى العمل الذي قاموا به في a11y-الحوار - مربع حوار مشروط يمكن الوصول إليه. وهي متوفرة على Github و NPM.

 npm i inclusive-menu-button --save

بالإضافة إلى ذلك ، ابتكرت Kitty إصدارًا من React لإمتاعك.

قائمة تدقيق

  • لا تستخدم دلالات قائمة ARIA في أنظمة قائمة التنقل.
  • في المواقع ذات المحتوى الثقيل ، لا تخفي البنية بعيدًا في قوائم التنقل المتداخلة القائمة على القائمة المنسدلة.
  • استخدم aria-expanded للإشارة إلى حالة الفتح / الإغلاق لقائمة التنقل التي يتم تنشيطها بواسطة الزر.
  • تأكد من أن قائمة التنقل المذكورة هي التالية في ترتيب التركيز بعد الزر الذي يفتحها / يغلقها.
  • لا تضحي أبدًا بقابلية الاستخدام في السعي وراء حلول خالية من JavaScript. إنه الغرور.