סקירה בסיסית שמראה איך ליצור רכיב רספונסיבי ונגיש של מיקומים באתר שיאפשרו למשתמשים לנווט באתר.
בפוסט הזה אני רוצה לשתף חשיבה על דרך לבניית רכיבי נתיב ניווט. רוצים לנסות את ההדגמה?
אם ברצונך ליצור סרטון, הנה גרסת YouTube של הפוסט הזה:
סקירה כללית
רכיב של נתיבי ניווט מראה איפה נמצא המשתמש בהיררכיית האתר, שנקראה על ידי הנסל וגרטל, שהשאירו נתיבי ניווט מאחור בחלק ביערות כהים והצליחו למצוא את הדרך הביתה על ידי מעקב אחר פירורים אחורה.
נתיבי הניווט בפוסט הזה אינם נתיבי ניווט רגילים, הם דומים לנתיבי ניווט. הם כוללים פונקציונליות נוספת, כי הם מוסיפים דפים-אחים ישירות בסרגל הניווט באמצעות <select>
, וכך מתאפשרת גישה רב-שכבתית.
חוויית משתמש ברקע
בסרטון ההדגמה של הרכיבים שלמעלה, קטגוריות ה-placeholder הן ז'אנרים של משחקי וידאו. השביל הזה נוצר על ידי ניווט בנתיב הבא: home »
rpg » indie » on sale
, כפי שמוצג בהמשך.
רכיב נתיב הניווט אמור לאפשר למשתמשים לעבור בהיררכיית המידע הזו, לדלג על הסתעפויות ולבחור דפים במהירות ובדיוק.
ארכיטקטורת מידע
מומלץ לשים לב לאוספים ולפריטים.
אוספים
קולקציה כוללת מגוון אפשרויות לבחירה. מדף הבית של אב-הטיפוס של פוסט זה, האוספים הם FPS, RPG, brawler, סורק מבוכים, ספורט ופאזלים.
פריטים
משחק וידאו הוא פריט. אוסף מסוים יכול להיות גם פריט אם הוא מייצג אוסף אחר. לדוגמה, משחקי תפקידים הם פריט ואוסף חוקי. כאשר מדובר בפריט, המשתמש נמצא בדף האוסף. לדוגמה, הם נמצאים בדף RPG, שבו מוצגת רשימה של משחקי תפקידים, כולל קטגוריות המשנה הנוספות AAA, Indie ו-Self Published.
במונחים של מדעי המחשב, רכיב נתיבי הניווט מייצג מערך רב-ממדי:
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>
מגדירים פעם אחת, משתמשים כמה פעמים שרוצים, עם השפעה מינימלית על ביצועי הדף וסגנון גמיש. הודעה aria-hidden="true"
נוספה לרכיב SVG.
הסמלים לא מתאימים למי שגולש ורק שומע את התוכן, והסתרת המשתמשים האלה מונעת מהם להוסיף רעש מיותר.
קישור מפוצל .crumb
כאן התפצלו נתיב הניווט המסורתי ואלה שברכיב הזה.
בדרך כלל, הקישור הזה יהיה רק <a>
, אבל הוספתי חוויית מעבר עם בחירה מוסתרת. המחלקה .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>
מועילה למשתמשים בקורא מסך, וכך מספקת להם מידע על הפעולה שמתרחשת בלחצן. עם זאת, הוא נותן את אותה עזרה גם לכולם, ב-iPad יופיע בחזית ובמרכז. מאפיין אחד מספק הקשר עבור לחצנים למשתמשים רבים.
קישוטים למפרידים
<span class="crumb-separator" aria-hidden="true">→</span>
לא חובה להוסיף סימני הפרדה, אבל גם אחד מהם עובד טוב (ראו דוגמה שלישית בסרטון שלמעלה). לאחר מכן אני נותנת כל aria-hidden="true"
, כי הם דקורטיביים ולא משהו שקורא המסך צריך להכריז.
המאפיין gap
, שמפורט בקטע הבא, מאפשר להוסיף בקלות רווחים בין התווים האלה.
סגנונות
מכיוון שהצבע משתמש בצבעי המערכת, הוא בעיקר מכיל פערים ומקבצים עבור סגנונות!
הכיוון והזרימה של הפריסה
רכיב הניווט הראשי nav.breadcrumbs
מגדיר מאפיין מותאם אישית בהיקף שבו הילדים יכולים להשתמש, אחרת יוצר פריסה אופקית אנכית. כך ניתן להבטיח שהפירורים, המחיצות והסמלים יישרו.
.breadcrumbs {
--nav-gap: 2ch;
display: flex;
align-items: center;
gap: var(--nav-gap);
padding: calc(var(--nav-gap) / 2);
}
כל .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
ראשית, ללא קשר לסוג הנתב שבו אתם משתמשים באתר או באפליקציה, כשמשתמש משנה את מיקומי הניווט, צריך לעדכן את כתובת ה-URL ולהציג את הדף המתאים למשתמש. שנית, כדי לנרמל את חוויית המשתמש, צריך לוודא שלא מתרחשים ניווטים לא צפויים כשמשתמשים רק מעיינים באפשרויות של <select>
.
ב-JavaScript יש שני אמצעים קריטיים לטיפול בחוויית המשתמש: הבחירה השתנתה ומניעה של הפעלת אירועי שינוי מסוג <select>
מתוך כוונה חזקה.
כדי למנוע אירועים נלווים, צריך להשתמש באלמנט <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>
מופעל.
סיכום
עכשיו, אחרי שאת יודעת איך עשיתי את זה, איך היית רוצה ‽ 🙂
בואו נגוון את הגישות שלנו ונלמד את כל הדרכים לבנות באינטרנט. צור הדגמה (דמו), ציוץ לי קישורים ואני אוסיף אותה לקטע 'רמיקסים של הקהילה' למטה!
רמיקסים של הקהילה
- Tux Solbakk כרכיב אינטרנט: הדגמה וקוד