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

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

في هذه المشاركة، أود أن أشارككم كيف صممتُ نموذجًا أوليًا لمكون 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.

الخاتمة

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

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

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