סקירה כללית בסיסית של אופן הבנייה של תפריט צד רספונסיבי שנפתח בהזזה
בפוסט הזה אני רוצה לשתף אתכם איך יצרתי אב טיפוס של רכיב Sidenav לאינטרנט שהוא רספונסיבי, מבוסס-מצב, תומך בניווט באמצעות מקלדת, פועל עם JavaScript ובלעדיו, ופועל בדפדפנים שונים. כדאי לנסות את ההדגמה.
אם אתם מעדיפים לצפות בסרטון, הנה גרסת YouTube של הפוסט הזה:
סקירה כללית
קשה לבנות מערכת ניווט רספונסיבית. חלק מהמשתמשים יסתמכו על מקלדת, חלקם ישתמשו במחשבים חזקים וחלקם יבקרו באתר ממכשיר נייד קטן. כל מי שנכנס לאתר צריך להיות מסוגל לפתוח ולסגור את התפריט.
Web Tactics
בניתוח הרכיבים הזה נהניתי לשלב כמה תכונות קריטיות של פלטפורמת האינטרנט:
- CSS
:target
- CSS grid
- טרנספורמציות של CSS
- שאילתות מדיה של CSS לגבי אזור התצוגה והעדפות המשתמש
- JS for
focus
UX enhancements
הפתרון שלי כולל סרגל צד אחד, והוא מופעל רק כשרוחב אזור התצוגה הוא 540px
או פחות.
540px
יהיה נקודת עצירה למעבר בין הפריסה האינטראקטיבית לנייד לבין הפריסה הסטטית למחשב.
פסאודו מחלקה של CSS :target
קישור <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>
כשלוחצים על כל אחד מהקישורים האלה, משתנה מצב הגיבוב של כתובת ה-URL של הדף שלנו, ואז אני משתמש במחלקת פסאודו כדי להציג ולהסתיר את סרגל הצד:
@media (max-width: 540px) {
#sidenav-open {
visibility: hidden;
}
#sidenav-open:target {
visibility: visible;
}
}
CSS Grid
בעבר, השתמשתי רק בפריסות וברכיבים של סרגלי צד עם מיקום מוחלט או קבוע. לעומת זאת, ב-Grid, עם התחביר grid-area
, אפשר להקצות כמה רכיבים לאותה שורה או לאותה עמודה.
מקבצים
רכיב הפריסה הראשי #sidenav-container
הוא רשת שיוצרת שורה אחת ו-2 עמודות, כאשר כל אחת מהן נקראת 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 3D transforms & transitions
הפריסה שלנו מוצגת עכשיו בערימה בגודל אזור התצוגה בנייד. עד שאוסיף סגנונות חדשים, הוא מוצג כברירת מחדל על גבי המאמר. בקטע הבא אציג כמה דוגמאות של חוויית משתמש שאני רוצה להשיג:
- הנפשה של פתיחה וסגירה
- הנפשה עם תנועה רק אם המשתמש מאשר זאת
- הפעלת אנימציה של
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 בסגירה, כאילו התפריט מעולם לא נפתח.
חוויית משתמש במצב פוקוס
הקטע הבא עוזר לנו להתמקד בכפתורי הפתיחה והסגירה אחרי שהם נפתחים או נסגרים. אני רוצה להקל על המעבר בין המצבים.
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, לצייץ לי את הגרסה שלך ואוסיף אותה לקטע Community remixes שבהמשך.
רמיקסים מהקהילה
- @_developit עם רכיבים מותאמים אישית: הדגמה וקוד
- @mayeedwin1 עם HTML/CSS/JS: הדגמה וקוד
- @a_nurella עם רמיקס ב-Glitch: הדגמה וקוד
- @EvroMalarkey עם HTML/CSS/JS: הדגמה וקוד