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

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

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

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

نظرة عامة

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

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

أساليب الويب

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

  1. خدمة مقارنة الأسعار (CSS) :target
  2. شبكة CSS
  3. transforms CSS
  4. طلبات بحث وسائط CSS لإطار العرض والإعدادات المفضّلة للمستخدم
  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 كي لا يدخل تركيز لوحة المفاتيح إلى العنصر خارج الشاشة.

عندما أبدأ في تنفيذ الرسوم المتحركة للحركة، أريد أن أبدأ بإمكانية الوصول في مقدمة أولوياتي.

حركة يسهل الوصول إليها

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

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

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

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

التحوّل أو التحويل أو الترجمة

Sidenav out (تلقائي)

لضبط الحالة التلقائية للتنقّل الجانبي على الأجهزة الجوّالة على حالة خارج الشاشة، أضع العنصر باستخدام 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 في

عندما يتطابق العنصر #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.

الخلاصة

والآن بعد أن عرفت كيف فعلت ذلك، كيف حالك؟! هذا يجعل بعض البنية الممتعة للمكونات! من سينشئ الإصدار الأول الذي يتضمّن خانات؟ 🙂

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

ريمكسات من إنشاء المنتدى