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

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

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

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

סקירה כללית

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

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

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

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

  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

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

  • אנימציה של פתיחה וסגירה
  • להוסיף אנימציה עם תנועה רק אם המשתמש מסכים לכך
  • אנימציה של 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 = '';
});
היסטוריית הדפדפן

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

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

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