Codelab: בניית רכיב Sidenav

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

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

הגדרה

  1. לוחצים על Remix to Edit כדי לאפשר עריכה של הפרויקט.
  2. פתיחת app/index.html.

HTML

קודם כול, צריך להבין את היסודות של הגדרת ה-HTML כדי שיהיה תוכן ותיבות לעבודה איתן.

מוסיפים את קוד ה-HTML הבא לתג <body>.

<aside></aside>
<main></main>

ה-<aside> מכיל את תפריט הניווט כרכיב משלים ל-<main>, שמכיל את תוכן הדף הראשי.

בשלב הבא נמלא את הרכיבים הסמנטיים האלה בשאר תוכן הדף.

מוסיפים רכיב ניווט, כמה קישורי ניווט וקישור לסגירה בתוך האלמנט <aside>.

<aside>
  <nav>
    <h4>My</h4>
    <a href="#">Dashboard</a>
    <a href="#">Profile</a>
    <a href="#">Preferences</a>
    <a href="#">Archive</a>

    <h4>Settings</h4>
    <a href="#">Accessibility</a>
    <a href="#">Theme</a>
    <a href="#">Admin</a>
  </nav>

  <a href="#"></a>
</aside>

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

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

<main>
  <header>
    <a href="#sidenav-open" class="hamburger">
      <svg viewBox="0 0 50 40">
        <line x1="0" x2="100%" y1="10%" y2="10%" />
        <line x1="0" x2="100%" y1="50%" y2="50%" />
        <line x1="0" x2="100%" y1="90%" y2="90%" />
      </svg>
    </a>
    <h1>Site Title</h1>
  </header>

  <article>
    {put some placeholder content here}
  </article>
</main>

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

ברכיב <article>, הדבקנו משפט placeholder. מחליפים את '‎' בתוכן משלכם, או מדביקים את הטקסט הבא:

<h2>Totam Header</h2>
<p>Lorem ipsum dolor, sit amet consectetur adipisicing elit. Cum consectetur, necessitatibus velit officia ut impedit veritatis temporibus soluta? Totam odit cupiditate facilis nisi sunt hic necessitatibus voluptatem nihil doloribus! Enim.</p>
<p>Lorem ipsum dolor sit, amet consectetur adipisicing elit. Fugit rerum, amet odio explicabo voluptas eos cum libero, ex esse quasi optio incidunt soluta eligendi labore error corrupti! Dolore, cupiditate porro.</p>

<h3>Subhead Totam Odit</h3>
<p>Lorem ipsum dolor sit, amet consectetur adipisicing elit. Fugit rerum, amet odio explicabo voluptas eos cum libero, ex esse quasi optio incidunt soluta eligendi labore error corrupti! Dolore, cupiditate porro.</p>
<p>Lorem ipsum dolor sit, amet consectetur adipisicing elit. Fugit rerum, amet odio explicabo voluptas eos cum libero, ex esse quasi optio incidunt soluta eligendi labore error corrupti! Dolore, cupiditate porro.</p>

<h3>Subhead</h3>
<p>Lorem ipsum dolor sit, amet consectetur adipisicing elit. Fugit rerum, amet odio explicabo voluptas eos cum libero, ex esse quasi optio incidunt soluta eligendi labore error corrupti! Dolore, cupiditate porro.</p>
<p>Lorem ipsum dolor sit, amet consectetur adipisicing elit. Fugit rerum, amet odio explicabo voluptas eos cum libero, ex esse quasi optio incidunt soluta eligendi labore error corrupti! Dolore, cupiditate porro.</p>

התוכן הזה, ואורכו, הם הגורמים לכך שאפשר יהיה לגלול בדף אם הוא חורג מגובה אזור התצוגה.

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

מוסיפים את המאפיינים title ו-aria-label לאלמנט הקישור הפתוח בכותרת:

<a href="#sidenav-open" class="hamburger">
<a href="#sidenav-open" title="Open Menu" aria-label="Open Menu" class="hamburger">

אפשר גם לסמן את סמל ה-SVG הפתוח בצורה ברורה יותר. מוסיפים את המאפיינים הבאים ל-SVG בתוך אלמנט הקישור הפתוח:

<svg viewBox="0 0 50 40">
<svg viewBox="0 0 50 40" role="presentation" focusable="false" aria-label="trigram for heaven symbol">

הקישור לסגירה בסרגל הצד יכול להיות מסומן בצורה ברורה יותר. מוסיפים את המאפיינים title ו-aria-label לאלמנט הקישור לסגירת תפריט הצד:

<a href="#"></a>
<a href="#" title="Close Menu" aria-label="Close Menu"></a>

CSS

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

מוסיפים את הקוד הבא ב-CSS אל css/sidenav.css כדי שרכיב <body> יארגן את הצאצאים.

body {
  display: grid;
  grid: [stack] 1fr / min-content [stack] 1fr;

  @media (max-width: 540px) {
    & > :matches(aside, main) {
      grid-area: stack;
    }
  }
}

למעשה, הפריסה הזו אומרת: יוצרים שורה בעלת שם stack עם כל מה שרוצים, ושתי עמודות בשורה הזו, כשהעמודה השנייה נקראת גם stack. צריך לקבוע את הגודל של העמודה הראשונה בהתאם לצורכי התוכן המינימליים שלה, והעמודה השנייה יכולה להכיל את שאר התוכן. לאחר מכן, אם חלון הצפייה מוגבל ל-540px או פחות, צריך להציב את תפריט הצד ואת רכיבי התוכן הראשי באותה שורה ובאותה עמודה, כך שהם יופיעו זה מעל זה בתצוגת רשת של 1x1.

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

מעדכנים את הרכיב <aside> ב-app/index.html:

<aside>
<aside id="sidenav-open">

כך ה-CSS יכול להתאים אלמנט ל-hash של כתובת ה-URL ביחד. הדבר חשוב לשימוש ב-:target. עכשיו המזהה של הרכיב יכול להתאים ל-hash של כתובת ה-URL שנקבע באמצעות תגי <a>.

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

<a href="#sidenav-open" class="hamburger" title="Open Menu" aria-label="Open Menu">
<a href="#sidenav-open" id="sidenav-button" class="hamburger" title="Open Menu" aria-label="Open Menu">

בשלב הבא, מוסיפים מזהה לקישור הסגירה של סרגל הניווט הצדדי:

<a href="#" title="Close Menu" aria-label="Close Menu"></a>
<a href="#" id="sidenav-close" title="Close Menu" aria-label="Close Menu"></a>

זהו הסיכום של הפריסה הרספונסיבית של המאקרו <body>, שמקשרת אותנו לסרגל כתובות ה-URL. קדימה, נמשיך!

ל-<aside> יש גם פריסה מסודרת. יש לו 2 רכיבי צאצא: <nav>, שהוא הרכיב שנראה כמו נייר ומצמיד את התפריט, ואלמנט קישור <a> סוגר שמגדיר את כתובת ה-URL כ-#. הקישור מוסתר מימין לתפריט הנפתח של נייר המידע, כדי שאנשים יוכלו ללחוץ על הרכיב החזותי כדי לסגור אותו.

מוסיפים את הקוד הבא לקובץ ה-CSS ב-css/sidenav.css:

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

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

בשלב הבא צריך להציג שכבת-על מותנית של התוכן הראשי ולשמור את המיקום שלי במהלך גלילה במסמך. זו עבודה מצוינת ל-position: sticky ולחלק מה-overscroll-behavior.

מוסיפים את הסגנונות הבאים לתפריט הצד:

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

  @media (max-width: 540px) {
    position: sticky;
    top: 0;
    max-height: 100vh;
    overflow: hidden auto;
    overscroll-behavior: contain;

    visibility: hidden; /* not keyboard accessible when closed */
  }
}

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

מוסיפים בורר מדומה מסוג :target לרכיב #sidenav-open:

#sidenav-open {

  @media (max-width: 540px) {

    &:target {
      visibility: visible;
    }
  }
}

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

מוסיפים את הקוד הבא לקובץ ה-CSS app/sidenav.css:

#sidenav-button,
#sidenav-close {
  -webkit-tap-highlight-color: transparent;
  -webkit-touch-callout: none;
  user-select: none;
  touch-action: manipulation;

  @media (min-width: 540px) {
    display: none;
  }
}

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

כדי להוסיף קצת עניין, נוסיף טרנספורמציות CSS עם נגישות הולמת. מוסיפים את הקוד הבא לקובץ ה-CSS ב-css/sidenav.css:

#sidenav-open {
  --easeOutExpo: cubic-bezier(0.16, 1, 0.3, 1);
  --duration: .6s;

  ...

  @media (max-width: 540px) {
    ...

    transform: translateX(-110vw);
    will-change: transform;
    transition:
      transform var(--duration) var(--easeOutExpo),
      visibility 0s linear var(--duration);

    &:target {
      visibility: visible;
      transform: translateX(0);
      transition: transform var(--duration) var(--easeOutExpo);
    }
  }

  @media (prefers-reduced-motion: reduce) {
    --duration: 1ms;
  }
}
הדגמה של האינטראקציה עם משך זמן מוגדר וללא משך זמן מוגדר, על סמך שאילתה של מדיה מסוג 'prefers-reduced-motion'.

מוסיפים קצת JavaScript

המפתח Escape אמור לסגור את התפריט. מוסיפים את הקוד הבא ב-JS אל js/index.js:

const sidenav = document.querySelector('#sidenav-open');

sidenav.addEventListener('keyup', e => {
  if (e.code === 'Escape') {
    document.location.hash = '';
  }
});

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

החלק הבא של UX JS הוא ניהול המיקוד. אני רוצה שהפתיחה והסגירה יהיו קלות, ולכן אני מחכה עד שחלונית הניווט הצדדית מסיימת מעבר כלשהו, ואז בודק אותה מול גיבוב כתובת ה-URL כדי לקבוע אם היא פתוחה או סגורה. לאחר מכן משתמשים ב-JavaScript כדי להעביר את המיקוד ללחצן שמתאים ללחצן שאליו הלקוח לחץ.

מוסיפים את הקוד הבא ב-JavaScript אל js/index.js:

const closenav = document.querySelector('#sidenav-close');
const opennav = document.querySelector('#sidenav-button');

sidenav.addEventListener('transitionend', e => {
  if (e.propertyName !== 'transform') {
    return;
  }

  const isOpen = document.location.hash === '#sidenav-open';

  isOpen
    ? closenav.focus()
    : opennav.focus();
});

רוצה לנסות?

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

סיכום

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

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

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