בניית רכיב של נתיבי ניווט

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

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

הדגמה

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

סקירה כללית

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

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

חוויית המשתמש ברקע

בסרטון ההדגמה של הרכיבים שלמעלה, קטגוריות ה-placeholder הן הז'אנרים של משחקי וידאו. השביל הזה נוצר על ידי ניווט בנתיב הבא: home » rpg » indie » on sale, כפי שמוצג בהמשך.

רכיב המיקום באתר אמור לאפשר למשתמשים לעבור היררכיית מידע; הסתעפויות מהירות ובחירת דפים במהירות מדויקות.

ארכיטקטורת מידע

לדעתי כדאי להתייחס לאוספים ולפריטים.

אוספים

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

פריטים

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

במונחים של מדעי המחשב, רכיב מיקומים באתר מייצג רב-מימדי מערך:

const rawBreadcrumbData = {
  "FPS": {...},
  "RPG": {
    "AAA": {...},
    "indie": {
      "new": {...},
      "on sale": {...},
      "under 5": {...},
    },
    "self published": {...},
  },
  "brawler": {...},
  "dungeon crawler": {...},
  "sports": {...},
  "puzzle": {...},
}

לאפליקציה או לאתר שלכם תהיה ארכיטקטורת מידע מותאמת אישית (IA) שיוצרת הוא מערך רב-מימדי שונה, אבל אני מקווה שהמושג 'נחיתה של אוסף' דפים ומעבר היררכי יכולים להופיע גם בנתיבי הניווט שלך.

פריסות

Markup

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

עיצוב כהה ובהיר

<meta name="color-scheme" content="dark light">

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

<nav class="breadcrumbs" role="navigation"></nav>

מומלץ להשתמש אלמנט <nav> לניווט באתר, שתפקידו ב-ARIA מרומז ניווט. בבדיקה שמתי לב שהמאפיין role שינה את האופן שבו הייתה אינטראקציה עם קורא מסך עם האלמנט, ולמעשה הוא הוכרז כ- ולכן בחרתי להוסיף אותו.

סמלים

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

כדי להשתמש בשיטה הזו, מוסיפים רכיב SVG מוסתר לדף ועוטפים את הסמלים ברכיב <symbol> עם מזהה ייחודי:

<svg style="display: none;">

  <symbol id="icon-home">
    <title>A home icon</title>
    <path d="M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6"/>
  </symbol>

  <symbol id="icon-dropdown-arrow">
    <title>A down arrow</title>
    <path d="M19 9l-7 7-7-7"/>
  </symbol>

</svg>

הדפדפן קורא את ה-HTML ב-SVG, שומר את פרטי הסמל לזיכרון, ממשיך לעמוד בשאר הדף שיש בו הפניה למזהה לשימושים נוספים של הסמל, כך:

<svg viewBox="0 0 24 24" width="24" height="24" aria-hidden="true">
  <use href="#icon-home" />
</svg>

<svg viewBox="0 0 24 24" width="24" height="24" aria-hidden="true">
  <use href="#icon-dropdown-arrow" />
</svg>

כלי פיתוח שמציגים רכיב SVG מעובד.

מגדירים פעם אחת, משתמשים כמה פעמים שרוצים, עם השפעה מינימלית על ביצועי הדף וסגנון גמיש. שימו לב שהמאפיין aria-hidden="true" נוסף לרכיב ה-SVG. הסמלים אינם שימושיים למשתמש שגולש שרק שומע את התוכן, והוא מסתיר בקרב המשתמשים האלה, מונע מהם להוסיף רעש מיותר.

כאן מתפצלים נתיב הניווט המסורתי ואלה שכלולים ברכיב הזה. בדרך כלל זה יהיה רק קישור <a>, אבל הוספתי UX למעבר עם בחירה מוסתרת. הכיתה .crumb אחראית לפריסה של הקישור, הסמל, ואילו .crumbicon אחראי לערום את הסמל ולבחור רכיב אחד. קראתי לזה קישור מפוצל כי הפונקציות שלו שדומה ללחצן מפוצל, אלא לניווט בדפים.

<span class="crumb">
  <a href="#sub-collection-b">Category B</a>
  <span class="crumbicon">
    <svg>...</svg>
    <select class="disguised-select" title="Navigate to another category">
      <option>Category A</option>
      <option selected>Category B</option>
      <option>Category C</option>
    </select>
  </span>
</span>

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

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

קישוטים למפריד

<span class="crumb-separator" aria-hidden="true">→</span>

לא חובה להוסיף מפרידים, אבל מומלץ להוסיף רק פריט אחד (ראו את הדוגמה השלישית בסרטון). שלמעלה). לאחר מכן אני נותן לכל aria-hidden="true" כי הם דקורטיביים ולא משהו שקורא מסך צריך להכריז עליו.

המאפיין gap, שמופיע בהמשך, מאפשר ריווח ברור בין הנתונים האלו.

סגנונות

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

הכיוון והזרימה של הפריסה

כלי פיתוח שבהם מוצג יישור של נתיב הניווט בשכבת-על של Flexbox
.

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

.breadcrumbs {
  --nav-gap: 2ch;

  display: flex;
  align-items: center;
  gap: var(--nav-gap);
  padding: calc(var(--nav-gap) / 2);
}

נתיב ניווט אחד מוצג לאורך עם שכבות-על של Flexbox.

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

.crumb {
  display: inline-flex;
  align-items: center;
  gap: calc(var(--nav-gap) / 4);

  & > a {
    white-space: nowrap;

    &[aria-current="page"] {
      font-weight: bold;
    }
  }
}

בוצעה הוספה של aria-current="page" כדי שהקישור לדף הנוכחי יבלוט יותר מנוחה. לא רק למשתמשים בקורא מסך יהיה אינדיקטור ברור לכך שהקישור בדף הנוכחי, עיצבנו באופן חזותי את הרכיב כדי לעזור למשתמשים לראות יקבלו חוויית משתמש דומה.

הרכיב .crumbicon משתמש ברשת כדי לערום סמל SVG עם ' כמעט מוסתר" רכיב <select>.

כלי הפיתוח לרשת מוצגים כשכבת-על מעל לחצן שבו גם השורה וגם העמודה
מקבץ עם שם.

.crumbicon {
  --crumbicon-size: 3ch;

  display: grid;
  grid: [stack] var(--crumbicon-size) / [stack] var(--crumbicon-size);
  place-items: center;

  & > * {
    grid-area: stack;
  }
}

הרכיב <select> הוא האחרון ב-DOM, ולכן הוא נמצא מעל המקבץ, ואינטראקטיביות. צריך להוסיף סגנון של opacity: .01 כדי שעדיין אפשר יהיה להשתמש ברכיב והתוצאה היא תיבה שמתאימה בדיוק לצורת הסמל. זוהי דרך נחמדה להתאים אישית את המראה של רכיב <select> בזמן ושמירה על הפונקציונליות המובנית.

.disguised-select {
  inline-size: 100%;
  block-size: 100%;
  opacity: .01;
  font-size: min(100%, 16px); /* Defaults to 16px; fixes iOS zoom */
}

אפשרויות נוספות

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

.breadcrumbs {
  overflow-x: auto;
  overscroll-behavior-x: contain;
  scroll-snap-type: x proximity;
  scroll-padding-inline: calc(var(--nav-gap) / 2);

  & > .crumb:last-of-type {
    scroll-snap-align: end;
  }

  @supports (-webkit-hyphens:none) { & {
    scroll-snap-type: none;
  }}
}

האפשרויות הנוספות מגדירות את חוויית המשתמש הבאה:

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

שאילתות מדיה

התאמה עדינה לאזורי תצוגה קטנים יותר היא הסתרת ה'בית' תווית, יציאה רק הסמל:

@media (width <= 480px) {
  .breadcrumbs .home-label {
    display: none;
  }
}

אלה לצד מיקומים באתר עם ובלי תווית בית, עבור
להשוואה.

נגישות

תנועה

אין הרבה תנועה ברכיב הזה, אבל באמצעות בבדיקה של prefers-reduced-motion, אנחנו יכולים למנוע תנועה לא רצויה.

@media (prefers-reduced-motion: no-preference) {
  .crumbicon {
    transition: box-shadow .2s ease;
  }
}

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

JavaScript

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

שני מדדים חיוניים של חוויית המשתמש לטיפול על ידי JavaScript: בחירה השתנה וברצונך לשנות את מניעת הפעלת האירועים ל-<select>.

נדרשת מניעת אירוע eager עקב שימוש ב-<select> לרכיב מסוים. ב-Windows Edge, וככל הנראה גם בדפדפנים אחרים, בוחרים באפשרות changed האירוע מופעל כשהמשתמש מחפש אפשרויות באמצעות המקלדת. זאת הסיבה ש שנקרא eager, כי המשתמש בחר רק את האפשרות, למשל או מיקוד, אבל עדיין לא אישרו את הבחירה עם enter או עם click. נחוש הופך את התכונה הזו של שינוי קטגוריית רכיבים לבלתי נגישה, כי פתיחת תיבת הבחירה ועיון בפריט פשוט תפעיל את האירוע לשנות את הדף לפני שהמשתמש מוכן.

אירוע <select> טוב יותר שהשתנה

const crumbs = document.querySelectorAll('.breadcrumbs select')
const allowedKeys = new Set(['Tab', 'Enter', ' '])
const preventedKeys = new Set(['ArrowUp', 'ArrowDown'])

// watch crumbs for changes,
// ensures it's a full value change, not a user exploring options via keyboard
crumbs.forEach(nav => {
  let ignoreChange = false

  nav.addEventListener('change', e => {
    if (ignoreChange) return
    // it's actually changed!
  })

  nav.addEventListener('keydown', ({ key }) => {
    if (preventedKeys.has(key))
      ignoreChange = true
    else if (allowedKeys.has(key))
      ignoreChange = false
  })
})

האסטרטגיה הזו היא לעקוב אחר אירועי השבתה של המקלדת בכל <select> ולקבוע אם המקש שנלחץ היה אישור ניווט (Tab או Enter) או בניווט מרחבי (ArrowUp או ArrowDown). עכשיו אפשר החלטה סופית, הרכיב יכול להחליט להמתין או לעבור, כשהאירוע של הפעלות של רכיב <select>.

סיכום

עכשיו, אחרי שהסברתי איך עשיתי את זה, איך היית? 🙂

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

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