בניית רכיב סרגל צד

סקירה בסיסית של בניית שקף רספונסיבי כלפי סרגל הצד

בפוסט הזה רציתי לספר לך איך יצרתי אב-טיפוס לרכיב Sidenav של האינטרנט עם יכולת תגובה, שמירת מצב, תומך בניווט באמצעות מקלדת, פועל עם JavaScript ובלי JavaScript ופועל בדפדפנים שונים. אפשר לנסות את ההדגמה.

אם ברצונך ליצור סרטון, הנה גרסת YouTube של הפוסט הזה:

סקירה כללית

קשה לבנות מערכת ניווט רספונסיבית. חלק מהמשתמשים עובדים באמצעות מקלדת, חלק משתמשים במחשבים שולחניים רבי עוצמה, וחלקם מבקרים ממכשיר נייד קטן. כל מי שמבקר בתפריט צריך להיות מסוגל לפתוח ולסגור את התפריט.

הדגמה של פריסה רספונסיבית ממחשבים לניידים
עיצוב בהיר וכהה למטה ב-iOS וב-Android

טקטיקות אינטרנט

נהניתי מהשילוב של כמה תכונות חשובות של פלטפורמת האינטרנט במהלך המחקר הזה:

  1. שירות CSS :target
  2. רשת CSS
  3. transforms של CSS
  4. שאילתות מדיה של CSS לגבי אזור התצוגה והעדפות המשתמש
  5. JS עבור focus שיפורי חוויית משתמש

בפתרון שלי יש סרגל צד אחד שאפשר להחליף בין המצבים רק כשהוא באזור תצוגה 'נייד' שגודלו 540px או פחות. 540px יהיה נקודת העצירה שלנו למעבר בין הפריסה האינטראקטיבית לנייד לפריסה הסטטית של המחשב.

פסאודו-סיווג של שירות ה-CSS :target

קישור אחד מסוג <a> מגדיר את גיבוב (hash) של כתובת ה-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>

לחיצה על כל אחד מהקישורים האלו משנה את מצב הגיבוב של כתובת האתר של הדף, ולאחר מכן באמצעות פסאודו מחלקה אני מציג ומסתיר את סרגל הניווט:

@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> הוא האלמנט האנימציה שמכיל את הניווט הצדדי. יש לו 2 צאצאים: מאגר הניווט <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 in

כשהאלמנט #sidenav תואם לערך :target, מגדירים את המיקום translateX() לערך homebase 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 ב-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.

סיכום

עכשיו, אחרי שאת יודעת איך עשיתי את זה, איך היית רוצה?! יצרנו ארכיטקטורה כיפית של רכיבים! מי ייצור את הגרסה הראשונה עם משבצות? 🙂

בואו נגוון את הגישות שלנו נלמד את כל הדרכים לבנות את האינטרנט. עליכם ליצור גליץ', לשלוח לי ציוץ את הגרסה שלכם, ואני אוסיף אותה לקטע רמיקסים מהקהילה שבהמשך.

רמיקסים של הקהילה