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

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

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

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

نظرة عامة

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

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

أساليب الويب

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

  1. خدمة مقارنة الأسعار (CSS) :target
  2. شبكة CSS
  3. عمليات تحويل 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، باستخدام بنية 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 إلى "in" الموضع 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.

الخاتمة

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

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

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