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

סקירה כללית בסיסית על יצירת תפריט צד רספונסיבי שנפתח בתנועה

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

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

סקירה כללית

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

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

Web Tactics

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

  1. CSS :target
  2. grid ב-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 של כתובת ה-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> הוא רכיב האנימציה שמכיל את תפריט הניווט הצדדי. יש לו 2 צאצאים: מאגר הניווט <nav> בשם [nav] ורקע <a> בשם [escape], שמשמשים לסגירת התפריט.

#sidenav-open {
  display: grid;
  grid-template-columns: [nav] 2fr [escape] 1fr;
}

משנים את הערכים של 2fr ו-1fr כדי למצוא את היחס הרצוי לשכבת-העל של התפריט וללחצן הסגירה של המרחב השלילי.

דוגמה למה שקורה כשמשנים את היחס.

טרנספורמציות ומעברים תלת-ממדיים ב-CSS

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

  • אנימציה של פתיחה וסגירה
  • להוסיף אנימציה עם תנועה רק אם המשתמש מסכים לכך
  • אנימציה של 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, ומתבוננים ב-CSS כשהרכיב מחליק מהמיקום -110vw 'מחוץ למסך' למיקום 0 'במסך' מעל var(--duration) כשה-hash של כתובת ה-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 = '';
});
היסטוריית הדפדפן

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

<a href="#" id="sidenav-close" title="Close Menu" aria-label="Close Menu" onchange="history.go(-1)"></a>

הפעולה הזו תסיר את הרשומה של כתובת ה-URL בהיסטוריה בסגירה, כאילו התפריט לא נפתח אף פעם.

Focus UX

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

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

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