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

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

כדאי לקרוא את הפוסט בבלוג שלי, Building a Sidenav element (בניית רכיב Sidenav) כדי ללמוד על התכונות של פלטפורמת האינטרנט של CSS שנבחרו לבניית הרכיב הזה.

הגדרה

  1. לוחצים על רמיקס לעריכה כדי שיהיה אפשר לערוך את הפרויקט.
  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 שכוללת את כל מה שמופיע בה, ו-2 עמודות בשורה הזו, וגם השורה השנייה נקראת stack. גודל העמודה הראשונה צריך להיות מותאם לדרישות התוכן המינימליות שלה, והעמודה השנייה יכולה למלא את כל השאר. לאחר מכן, אם באזור תצוגה מוגבל של 540px או פחות, צריך למקם את רכיבי ה-sidenav ואת רכיבי התוכן העיקריים באותה שורה ועמודה, כך שהם ימוקמו זה על גבי זה ברשת של 1x1.

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

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

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

כך CSS יכול להתאים בין רכיב לבין גיבוב של כתובת URL. זה חשוב לשימוש ב-:target. עכשיו מזהה הרכיב יכול להתאים לקוד הגיבוב של כתובת ה-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> יש פריסה מסודרת. יש לו שני צאצאים: <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;
  }
}
הדגמה של האינטראקציה עם וללא משך זמן שהוחלה על סמך שאילתת המדיה 'העדפה לצמצום תנועה'.

מפוזר ב-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();
});

אני רוצה לנסות

  • כדי לראות תצוגה מקדימה של האתר, מקישים על View App ואז על Fullscreen מסך מלא.

סיכום

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

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

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