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

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

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

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

סקירה כללית

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

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

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

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

  1. שירות CSS :target
  2. רשת CSS
  3. שינויים בשירות 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>

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

@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.

סיכום

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

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

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