بناء مكون التنقل الجانبي

نظرة عامة أساسية حول كيفية إنشاء شريط جانبي قابل للانزلاق وسريع الاستجابة

في هذه المشاركة، أريد أن أشارك معك كيفية إنشاء نموذج أولي لمكوّن Sidenav للويب يكون سريع الاستجابة ويحتفظ بالحالة ويدعم التنقّل باستخدام لوحة المفاتيح ويعمل مع JavaScript وبدونه، ويتوافق مع جميع المتصفّحات. جرِّب الإصدار التجريبي.

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

نظرة عامة

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

عرض توضيحي لتنسيق الاستجابة للأجهزة الجوّالة على أجهزة الكمبيوتر المكتبي
تم إيقاف المظهرين الفاتح والداكن على أجهزة iOS وAndroid

Web Tactics

في استكشاف المكوّنات هذا، كان من دواعي سروري الجمع بين بعض الميزات المهمة لمنصّة الويب:

  1. CSS :target
  2. شبكة CSS
  3. عمليات التحويل في CSS
  4. طلبات CSS Media Queries لإطار العرض والإعدادات المفضّلة للمستخدم
  5. JavaScript focus لتحسينات تجربة المستخدم

يتضمّن الحلّ الذي أقدّمه شريطًا جانبيًا واحدًا ولا يتم تفعيله إلا عند عرض الصفحة على إطار "شاشة جهاز جوّال" أبعاده 540px أو أقل. ستكون 540px نقطة التوقف للتبديل بين التنسيق التفاعلي للأجهزة الجوّالة والتنسيق الثابت لأجهزة الكمبيوتر المكتبي.

الفئة الصورية :target في CSS

يضبط أحد الروابط <a> تجزئة عنوان URL على #sidenav-open والآخر على فارغ (''). أخيرًا، يحتوي العنصر على id لمطابقة التجزئة:

<a href="#sidenav-open" id="sidenav-button" title="Open Menu" aria-label="Open Menu">

<a href="#" id="sidenav-close" title="Close Menu" aria-label="Close Menu"></a>

<aside id="sidenav-open">
  …
</aside>

يؤدي النقر على كلٍّ من هذين الرابطَين إلى تغيير حالة علامة التجزئة لعنوان URL للصفحة، ثم باستخدام فئة زائفة، أعرض شريط التنقّل الجانبي وأخفيه:

@media (max-width: 540px) {
  #sidenav-open {
    visibility: hidden;
  }

  #sidenav-open:target {
    visibility: visible;
  }
}

شبكة CSS

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

الحِزم

عنصر التنسيق الأساسي #sidenav-container هو شبكة تنشئ صفًا واحدًا وعمودَين، ويُطلق على أحدهما اسم stack. عندما تكون المساحة محدودة، تحدّد CSS اسم الشبكة نفسه لجميع عناصر <main> التابعة، ما يؤدي إلى وضع جميع العناصر في المساحة نفسها وإنشاء حزمة.

#sidenav-container {
  display: grid;
  grid: [stack] 1fr / min-content [stack] 1fr;
  min-height: 100vh;
}

@media (max-width: 540px) {
  #sidenav-container > * {
    grid-area: stack;
  }
}

<aside> هو العنصر المتحرّك الذي يحتوي على شريط التنقّل الجانبي. يحتوي على عنصرَين فرعيَّين: حاوية التنقّل <nav> التي تحمل الاسم [nav] وخلفية <a> التي تحمل الاسم [escape]، والتي تُستخدَم لإغلاق القائمة.

#sidenav-open {
  display: grid;
  grid-template-columns: [nav] 2fr [escape] 1fr;
}

عدِّل 2fr و1fr للعثور على النسبة التي تفضّلها للعنصر المتراكب للقوائم وزر الإغلاق في المساحة السلبية.

عرض توضيحي لما يحدث عند تغيير النسبة

عمليات التحويل والانتقالات الثلاثية الأبعاد في CSS

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

  • إضافة تأثيرات متحركة للفتح والإغلاق
  • استخدام الصور المتحركة فقط إذا كان المستخدم موافقًا على ذلك
  • تحريك visibility لكي لا ينتقل تركيز لوحة المفاتيح إلى العنصر الذي لا يظهر على الشاشة

عندما أبدأ في تنفيذ الرسوم المتحركة، أريد التركيز على تسهيل الاستخدام.

مؤثرات حركية يسهل فهمها

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

#sidenav-open {
  --duration: .6s;
}

@media (prefers-reduced-motion: reduce) {
  #sidenav-open {
    --duration: 1ms;
  }
}
عرض توضيحي للتفاعل مع تطبيق المدة وعدم تطبيقها

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

الانتقال والتحويل والترجمة

شريط التنقّل الجانبي خارج الشاشة (تلقائي)

لضبط الحالة التلقائية للشريط الجانبي على الأجهزة الجوّالة على أنّها حالة "خارج الشاشة"، أضع العنصر باستخدام transform: translateX(-110vw).

يُرجى العِلم أنّني أضفت 10vw آخر إلى رمز -100vw المعتاد الذي يظهر خارج الشاشة، لضمان عدم ظهور box-shadow في شريط التنقّل الجانبي في مساحة العرض الرئيسية عندما يكون مخفيًا.

@media (max-width: 540px) {
  #sidenav-open {
    visibility: hidden;
    transform: translateX(-110vw);
    will-change: transform;
    transition:
      transform var(--duration) var(--easeOutExpo),
      visibility 0s linear var(--duration);
  }
}
شريط التنقّل الجانبي في

عندما يتطابق عنصر #sidenav مع :target، اضبط موضع translateX() على قاعدة العرض 0، وراقِب CSS وهي تُزِل العنصر من موضع "الخارج" -110vw إلى موضع "الداخل" 0 على var(--duration) عند تغيير تجزئة عنوان URL.

@media (max-width: 540px) {
  #sidenav-open:target {
    visibility: visible;
    transform: translateX(0);
    transition:
      transform var(--duration) var(--easeOutExpo);
  }
}

إذن الوصول إلى الانتقال

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

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

تحسينات على تجربة المستخدم في أدوات تسهيل الاستخدام

يعتمد هذا الحل على تغيير عنوان URL لإدارة الحالة. من الطبيعي أن يتم استخدام العنصر <a> هنا، ويحصل على بعض ميزات تسهيل الاستخدام مجانًا. لنضيف إلى عناصرنا التفاعلية تصنيفات توضّح الغرض منها بوضوح.

<a href="#" id="sidenav-close" title="Close Menu" aria-label="Close Menu"></a>

<a href="#sidenav-open" id="sidenav-button" class="hamburger" title="Open Menu" aria-label="Open Menu">
  <svg>...</svg>
</a>
فيديو تجريبي لتجربة المستخدم في ما يتعلّق بالتعليق الصوتي والتفاعل مع لوحة المفاتيح

توضِّح الآن أزرار التفاعل الأساسية الغرض منها بوضوح لكل من الماوس ولوحة المفاتيح.

:is(:hover, :focus)

يتيح لنا هذا العنصر الاختياري الوهمي الوظيفي لخدمة مقارنة الأسعار (CSS) أن نوفّر ميزات شاملة بسرعة من خلال أنماط التمرير فوق العناصر من خلال مشاركتها مع التركيز أيضًا.

.hamburger:is(:hover, :focus) svg > line {
  stroke: hsl(var(--brandHSL));
}

استخدام JavaScript

اضغط على escape للإغلاق

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

const sidenav = document.querySelector('#sidenav-open');

sidenav.addEventListener('keyup', event => {
  if (event.code === 'Escape') document.location.hash = '';
});
سجلّ المتصفح

لمنع التفاعل مع زرَّي الفتح والإغلاق من تجميع عدة إدخالات في سجلّ المتصفّح، أضِف مقتطف JavaScript التالي مضمّنًا في زر الإغلاق:

<a href="#" id="sidenav-close" title="Close Menu" aria-label="Close Menu" onchange="history.go(-1)"></a>

سيؤدي ذلك إلى إزالة إدخال سجلّ عناوين URL عند الإغلاق، ما يجعله يبدو كما لو لم يتم فتح القائمة مطلقًا.

تجربة المستخدم في "التركيز"

يساعدنا المقتطف التالي في التركيز على زرَّي الفتح والإغلاق بعد فتحهما أو إغلاقهما. أريد تسهيل التبديل.

sidenav.addEventListener('transitionend', e => {
  const isOpen = document.location.hash === '#sidenav-open';

  isOpen
      ? document.querySelector('#sidenav-close').focus()
      : document.querySelector('#sidenav-button').focus();
})

عند فتح شريط التنقّل الجانبي، ركِّز على زر الإغلاق. عند إغلاق شريط التنقّل الجانبي، ركِّز على زر الفتح. وأُجري ذلك من خلال استدعاء focus() على العنصر في JavaScript.

الخاتمة

الآن بعد أن عرفت كيف فعلت ذلك، كيف ستفعل ذلك؟ ويؤدي ذلك إلى إنشاء بنية مكونات ممتعة. مَن سينشئ النسخة الأولى التي تتضمّن خانات؟ 🙂

لننوّع الطرق التي نتّبعها ونتعرّف على جميع الطرق لإنشاء تطبيقات على الويب. أنشئ Glitch، ثمّ أرسِل إلينا تغريدة تتضمّن نسختك، وسنضيفها إلى القسم ريمكسات من إنشاء المنتدى أدناه.

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