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

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

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

في ما يلي إصدار YouTube من هذه المشاركة إذا كنت تفضّل الفيديوهات:

نظرة عامة

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

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

أساليب الويب

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

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

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

الفئة الزائفة في لغة CSS :target

يضبط أحد روابط <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;
  }
}
عرض توضيحي للتفاعل مع مرور المدة ومطبّقة بدونها.

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

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

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

لضبط الحالة التلقائية للتنقّل الجانبي على الأجهزة الجوّالة على حالة خارج الشاشة، أضع العنصر باستخدام 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، وعند تغيير تجزئة عنوان URL، يتم عرض العنصر #sidenav خارج موضع -110vw إلى موضع "in" عند 0 على var(--duration).

@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 وأرسِل تغريدة إلى نسختك وسنضيفها إلى قسم الريمكسات في "المنتدى" أدناه.

ريمكسات من المنتدى