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

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

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

אם אתם מעדיפים סרטון, הנה גרסה של YouTube לפוסט:

סקירה

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

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

טקטיקה באינטרנט

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

  1. שירות CSS :target
  2. רשת CSS
  3. transforms בשירות CSS
  4. שאילתות מדיה של CSS בנושא אזור תצוגה והעדפות משתמש
  5. JS עבור 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 הוא רשת שיוצרת שורה אחת ו-2 עמודות, שאחת מהן נקראת 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() כ-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 בסוף המעבר.

שיפורי UX לנגישות

הפתרון הזה מסתמך על שינוי כתובת ה-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, לשלוח לי ציוץ של הגרסה שלכם, ואני אוסיף אותה לקטע רמיקסים מהקהילה שבהמשך.

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