סקירה כללית בסיסית על יצירת רכיב כרטיסיות שדומה לרכיבים שנמצאים באפליקציות ל-iOS ול-Android.
בפוסט הזה אני רוצה לשתף את החשיבה שלי לגבי פיתוח רכיב כרטיסיות לאינטרנט שתומך במספר מקורות קלט במכשירים שונים ופועל בכל הדפדפנים. כדאי לנסות את הדגמה.
אם אתם מעדיפים סרטון, הנה גרסה של הפוסט הזה ב-YouTube:
סקירה כללית
כרטיסיות הן רכיב נפוץ במערכות עיצוב, אבל הן יכולות ללבוש צורות רבות. קודם היו כרטיסיות למחשב שנוצרו באמצעות הרכיב <frame>
, ועכשיו יש לנו רכיבים ניידים חלקים שמוסיפים אנימציה לתוכן על סמך מאפייני פיזיקה.
כולם מנסים לעשות את אותו הדבר: לחסוך מקום.
כיום, המרכיבים הבסיסיים של חוויית המשתמש בכרטיסיות הם אזור ניווט עם לחצנים שמאפשר להחליף את החשיפה של התוכן בפריים התצוגה. אזורי תוכן רבים שונים משתפים את אותו מרחב, אבל הם מוצגים באופן מותנה על סמך הלחצן שנבחר בתפריט הניווט.
Web Tactics
בסך הכול, מצאתי שהרכיב הזה קל ליצירה, בזכות כמה תכונות קריטיות בפלטפורמת האינטרנט:
scroll-snap-points
לאינטראקציות אלגנטיות של החלקה ומקלדת עם מיקומי עצירה מתאימים לגלילה- קישורי עומק באמצעות גיבוב של כתובות URL, לצורך עיגון גלילה בדף ותמיכה בשיתוף בדפדפן
- תמיכה בקורא מסך באמצעות רכיבי רכיבי
<a>
ו-id="#hash"
prefers-reduced-motion
להפעלת מעברים של מעבר הדרגתי (crossfade) וגלילה מיידית בדף- התכונה האינטרנטית
@scroll-timeline
בזמן הכתיבה ליצירת קו תחתון דינמי בכרטיסייה שנבחרה ולשינוי הצבע שלה
ה-HTML
באופן בסיסי, חוויית המשתמש כאן היא: לוחצים על קישור, כתובת ה-URL מייצגת את מצב הדף המוטמע ואז רואים את אזור התוכן מתעדכן כשהדפדפן גולל לאלמנט התואם.
יש שם כמה רכיבי תוכן מבניים: קישורים ו-:target
. אנחנו צריכים רשימה של קישורים, שאפשר ליצור בקלות באמצעות <nav>
, ורשימת רכיבי <article>
, שאפשר ליצור בקלות באמצעות <section>
. כל גיבוב קישור יתאים לקטע, וכך הדפדפן יוכל לגלול דרך עיגון.
לדוגמה, לחיצה על קישור ממקדת באופן אוטומטי את המאמר :target
ב-Chrome 89, ללא צורך ב-JS. לאחר מכן, המשתמש יוכל לגלול את תוכן המאמר באמצעות מכשיר הקלט שלו כרגיל. זהו תוכן משלים, כפי שמצוין ב-Markup.
השתמשתי בסימון הבא כדי לארגן את הכרטיסיות:
<snap-tabs>
<header>
<nav>
<a></a>
<a></a>
<a></a>
<a></a>
</nav>
</header>
<section>
<article></article>
<article></article>
<article></article>
<article></article>
</section>
</snap-tabs>
אפשר ליצור קישורים בין הרכיבים <a>
ו-<article>
באמצעות המאפיינים href
ו-id
באופן הבא:
<snap-tabs>
<header>
<nav>
<a href="#responsive"></a>
<a href="#accessible"></a>
<a href="#overscroll"></a>
<a href="#more"></a>
</nav>
</header>
<section>
<article id="responsive"></article>
<article id="accessible"></article>
<article id="overscroll"></article>
<article id="more"></article>
</section>
</snap-tabs>
לאחר מכן מילאתי את המאמרים בכמויות משתנות של טקסט לדוגמה, והקישורים בכמויות משתנות של כותרות עם תמונות. עכשיו, כשיש לנו תוכן לעבוד איתו, נוכל להתחיל בתכנון הפריסה.
פריסות גלילה
יש 3 סוגים שונים של אזורי גלילה ברכיב הזה:
- אפשר לגלול לאורך (ורוד) סרגל הניווט
- אפשר לגלול באזור התוכן (כחול) בכיוון אופקי
- אפשר לגלול אנכית בכל פריט של מאמר (ירוק).
יש 2 סוגים שונים של רכיבים שקשורים לגלילה:
- חלון
תיבה עם מאפייני גובה ורוחב מוגדרים, עם סגנון המאפייןoverflow
. - שטח גדול
בפריסה הזו, אלה מאגרי הרשימה: קישורי הניווט, המאמרים בקטע ותוכן המאמרים.
פריסה של <snap-tabs>
בחרתי בפריסה ברמה העליונה של Flex (Flexbox). הגדרתי את הכיוון ל-column
, כך שהכותרת והקטע מסודרים אנכית. זהו חלון הגלילה הראשון, והוא מסתיר את כל מה שמוצג מעבר לקצה המסך. בקרוב נוסיף גלילה מעבר לקצה לכותרת ולקטע, כתחומים נפרדים.
<snap-tabs> <header></header> <section></section> </snap-tabs>
snap-tabs { display: flex; flex-direction: column; /* establish primary containing box */ overflow: hidden; position: relative; & > section { /* be pushy about consuming all space */ block-size: 100%; } & > header { /* defend againstneeding 100% */ flex-shrink: 0; /* fixes cross browser quarks */ min-block-size: fit-content; } }
חזרה לתרשים הצבעוני עם 3 גלילות:
- עכשיו
<header>
מוכן לשמש כמאגר הגלילה (ורוד). <section>
מוכן להיות מאגר הגלילה (כחול).
המסגרות שסימנתי בהמשך באמצעות VisBug עוזרות לנו לראות את החלונות שנוצרו על ידי מאגרי הגלילה.
פריסת הכרטיסיות <header>
הפריסה הבאה דומה מאוד: אני משתמש ב-flex כדי ליצור סדר אנכי.
<snap-tabs> <header> <nav></nav> <span class="snap-indicator"></span> </header> <section></section> </snap-tabs>
header { display: flex; flex-direction: column; }
ה-.snap-indicator
צריך לנוע אופקית עם קבוצת הקישורים, וסידור הכותרת הזה עוזר ליצור את התרחיש הזה. אין כאן אלמנטים במיקום מוחלט!
בשלב הבא, סגנונות הגלילה. מסתבר שאפשר לשתף את סגנונות הגלילה בין שני אזורי הגלילה האופקיים (כותרת וקטע), אז יצרתי את .scroll-snap-x
, class של שירות.
.scroll-snap-x {
/* browser decide if x is ok to scroll and show bars on, y hidden */
overflow: auto hidden;
/* prevent scroll chaining on x scroll */
overscroll-behavior-x: contain;
/* scrolling should snap children on x */
scroll-snap-type: x mandatory;
@media (hover: none) {
scrollbar-width: none;
&::-webkit-scrollbar {
width: 0;
height: 0;
}
}
}
לכל אחד מהם צריך להגדיר זליגת-נתונים (overflow) בציר x, אזור גלילה מוגבל כדי למנוע גלילה מעבר לתוכן, סרגל גלילה מוסתר למכשירי מגע ולבסוף נעילה של אזורים להצגת תוכן. סדר הכרטיסיות במקלדת נגיש, וכל האינטראקציות מכוונות את המיקוד באופן טבעי. בנוסף, בקונטיינרים של Snaps לגלילה יש אינטראקציה נעימה בסגנון קרוסלה מהמקלדת.
פריסה של <nav>
בכותרת הכרטיסיות
קישורי הניווט צריכים להיות מסודרים בשורה, ללא מעברי שורה, מוצבים במרכז אנכית וכל פריט קישור צריך להיצמד לקונטיינר של נעילה בגלילה. עבודה מהירה ב-CSS לשנת 2021!
<nav> <a></a> <a></a> <a></a> <a></a> </nav>
nav { display: flex; & a { scroll-snap-align: start; display: inline-flex; align-items: center; white-space: nowrap; } }
כל קישור מגדיר את הסגנון והגודל שלו, כך שפריסת הניווט צריכה לציין רק את הכיוון והזרימה. רוחב ייחודי של פריטים בתפריט הניווט הופך את המעבר בין הכרטיסיות למהנה, כי הסמן משתנה בהתאם ליעד החדש. בהתאם למספר הרכיבים שבה, הדפדפן ידפיס פס גלילה או לא.
פריסת הכרטיסיות <section>
הקטע הזה הוא פריט גמיש, והוא צריך להיות הגורם העיקרי שצורך מקום. צריך גם ליצור עמודות שבהן יוצגו המאמרים. שוב, עבודה מהירה ב-CSS 2021! הרכיב block-size: 100%
ימתח את הרכיב הזה כדי למלא את הרכיב ההורה ככל האפשר, ואז ייצור סדרה של עמודות ברוחב 100%
של הרכיב ההורה לצורך הפריסה שלו. כאן אחוזים מתאימים מאוד כי כתבנו אילוצים חזקים על האב.
<section> <article></article> <article></article> <article></article> <article></article> </section>
section { block-size: 100%; display: grid; grid-auto-flow: column; grid-auto-columns: 100%; }
זה כמו שאנחנו אומרים "להרחיב אופקית ככל האפשר, באופן דחוף" (זכרו את הכותרת שהגדרתם ל-flex-shrink: 0
: היא הגנה מפני דחיפת ההרחבה הזו), שמגדיר את גובה השורה לקבוצה של עמודות בגובה מלא. הסגנון auto-flow
מציין שהרשת תמיד תציג את הצאצאים בשורה אופקית, ללא גלישת טקסט, בדיוק מה שאנחנו רוצים – כדי שהם יחרגו מחלון ההורה.
לפעמים קשה לי להבין את הדברים האלה. רכיב הקטע הזה נכנס לקופסה, אבל הוא גם יצר קבוצה של קופסאות. אני מקווה שההסברים והתמונות עוזרים לך.
פריסת הכרטיסיות <article>
המשתמש צריך להיות מסוגל לגלול את תוכן המאמר, וסרגלי הגלילה צריכים להופיע רק אם יש עודף תוכן. האלמנטים האלה במאמר ממוקמים בצורה מסודרת. הוא גם הורה גלילה וגם צאצא גלילה. הדפדפן מטפל כאן באינטראקציות מורכבות של מגע, עכבר ומקלדת.
<article> <h2></h2> <p></p> <p></p> <h2></h2> <p></p> <p></p> ... </article>
article { scroll-snap-align: start; overflow-y: auto; overscroll-behavior-y: contain; }
בחרתי שהכתבות יישארו במקום בתוך סרגל הגלילה של ההורה שלהן. אני מאוד אוהב את האופן שבו הפריטים של קישורי הניווט ורכיבי הכתבה נצמדים לתחילת השורה של מאגרי הגלילה המתאימים. נראה שזו מערכת יחסים הרמונית.
המאמר הוא רכיב צאצא של רשת, והגודל שלו נקבע מראש כשטח שדה התצוגה שבו אנחנו רוצים לספק חוויית משתמש של גלילה. המשמעות היא שאין צורך בסגנונות גובה או רוחב כאן, רק צריך להגדיר איך הוא יחרוג מעבר לגבולות. הגדרתי את overflow-y ל-auto, ואז גם לכדתי את האינטראקציות עם הגלילה באמצעות המאפיין השימושי overscroll-behavior.
סיכום של 3 אזורי גלילה
בהמשך, בחרתי בהגדרות המערכת שלי באפשרות 'הצגת סרגל גלילה תמיד'. לדעתי חשוב מאוד שהפריסה תפעל כשההגדרה הזו מופעלת, כי כך אוכל לבדוק את הפריסה ואת תזמון הגלילה.
לדעתי, הצגת פס ההזזה ברכיב הזה עוזרת להראות בבירור איפה נמצאים אזורי הגלילה, את הכיוון שהם תומכים בו ואת האינטראקציה ביניהם. חשוב לזכור שכל אחת מהמסגרות של חלונות הגלילה האלה היא גם הורה של פריסה ב-flex או ב-grid.
כלי הפיתוח יכולים לעזור לנו להציג את זה באופן חזותי:
פריסות הגלילה מושלמות: אפשר להצמיד אותן, ליצור אליהן קישורי עומק ולגשת אליהן באמצעות המקלדת. בסיס איתן לשיפור חוויית המשתמש, לסגנון ולעיצוב נעים.
תכונות עיקריות
ילדים שנמצאים ב-Scroll Snap שומרים על המיקום הנעילה שלהם במהלך שינוי הגודל. כלומר, לא תהיה צורך ב-JavaScript כדי להציג משהו במסך כשמסובבים את המכשיר או משנים את גודל הדפדפן. אפשר לנסות את התכונה במצב מכשיר של כלי הפיתוח של Chromium. לשם כך, בוחרים מצב אחר מלבד רספונסיבי ואז משנים את הגודל של מסגרת המכשיר. שימו לב שהאלמנט נשאר במסך ונעול עם התוכן שלו. התכונה הזו זמינה מאז ש-Chromium עדכן את ההטמעה שלו כך שתתאים למפרט. פוסט בבלוג בנושא
Animation
מטרת האנימציה כאן היא לקשר בבירור בין אינטראקציות לבין משוב על ממשק המשתמש. כך תוכלו להנחות את המשתמשים או לעזור להם לגלות את כל התוכן בצורה חלקה (כך אנחנו מקווים). אוסיף תנועה באופן מכוון ומותנה. עכשיו המשתמשים יכולים לציין את ההעדפות שלהם לגבי תנועה במערכת ההפעלה, ואני נהנה מאוד להגיב להעדפות שלהם בממשקים שלי.
אקשר קו תחתון בכרטיסייה למיקום הגלילה במאמר. הצמדה היא לא רק התאמה יפה, היא גם עיגון של ההתחלה והסיום של אנימציה.
כך ה-<nav>
, שמשמש כמפה מיני, יישאר מחובר לתוכן.
אנחנו נבדוק את העדפות התנועה של המשתמש גם מ-CSS וגם מ-JS. יש כמה מקומות נהדרים שבהם אפשר להפגין אדיבות.
התנהגות הגלילה
יש הזדמנות לשפר את התנהגות התנועה של :target
וגם של element.scrollIntoView()
. כברירת מחדל, ההעברה היא מיידית. הדפדפן רק מגדיר את מיקום הגלילה. מה קורה אם רוצים לעבור למיקום הגלילה הזה, במקום להבהב בו?
@media (prefers-reduced-motion: no-preference) {
.scroll-snap-x {
scroll-behavior: smooth;
}
}
מכיוון שאנחנו מוסיפים כאן תנועה, ותנועה שהמשתמש לא שולט בה (כמו גלילה), אנחנו מחילים את הסגנון הזה רק אם למשתמש אין העדפה במערכת ההפעלה שלו לגבי הפחתת תנועה. כך נוכל להציג את התנועה בגלילה רק לאנשים שמביעים הסכמה לכך.
אינדיקטור הכרטיסיות
מטרת האנימציה הזו היא לעזור לשייך את האינדיקטור למצב התוכן. החלטתי להשתמש בסגנונות של מעבר צבע border-bottom
למשתמשים שמעדיפים תנועה מופחתת, ובאנימציה של החלקה עם מעבר צבע למשתמשים שמוכנים לתנועה.
ב-Chromium Devtools אפשר להחליף את ההעדפה ולהדגים את 2 סגנונות המעבר השונים. נהניתי מאוד לבנות את זה.
@media (prefers-reduced-motion: reduce) {
snap-tabs > header a {
border-block-end: var(--indicator-size) solid hsl(var(--accent) / 0%);
transition: color .7s ease, border-color .5s ease;
&:is(:target,:active,[active]) {
color: var(--text-active-color);
border-block-end-color: hsl(var(--accent));
}
}
snap-tabs .snap-indicator {
visibility: hidden;
}
}
אני מסתיר את .snap-indicator
כשהמשתמש מעדיף תנועה מופחתת, כי כבר אין לי צורך בו. לאחר מכן מחליפים אותו בסגנונות border-block-end
וב-transition
. בנוסף, שימו לב שבאינטראקציה עם הכרטיסיות, פריט הניווט הפעיל לא רק מודגש בקו תחתון של המותג, אלא גם צבע הטקסט שלו כהה יותר. לרכיב הפעיל יש ניגודיות גבוהה יותר של צבע הטקסט והוא כולל תאורת רקע בהירה.
רק כמה שורות נוספות של CSS יגרמו לאנשים להרגיש שאנחנו רואים אותם (במובן שאנחנו מכבדים את העדפות התנועה שלהם). אני מת על זה.
@scroll-timeline
בקטע שלמעלה הראיתי איך מטפלים בסגנונות של מעבר חלק עם תנועה מופחתת, ובקטע הזה אראה איך קישרתי את האינדיקטור לאזור גלילה. עכשיו תכף אראה לך כמה דברים ניסיוניים ומעניינים. אני מקווה שגם את/ה מתרגש/ת.
const { matches:motionOK } = window.matchMedia(
'(prefers-reduced-motion: no-preference)'
);
קודם כול, אני בודק את העדפת התנועה של המשתמש באמצעות JavaScript. אם התוצאה היא false
, כלומר שהמשתמש מעדיף תנועה מופחתת, לא נפעיל אף אחד מהאפקטים של תנועה בקישור לגלילה.
if (motionOK) {
// motion based animation code
}
נכון למועד כתיבת שורות אלה, אין תמיכה בדפדפנים ב-@scroll-timeline
. זהו טיוטה של מפרט עם הטמעות ניסיוניות בלבד. עם זאת, יש לו polyfill שאני משתמש בו בדמו הזה.
ScrollTimeline
אפשר ליצור צירי זמן לגלילה גם ב-CSS וגם ב-JavaScript, אבל בחרתי ב-JavaScript כדי שאוכל להשתמש במדידות של רכיבים פעילים באנימציה.
const sectionScrollTimeline = new ScrollTimeline({
scrollSource: tabsection, // snap-tabs > section
orientation: 'inline', // scroll in the direction letters flow
fill: 'both', // bi-directional linking
});
אני רוצה שדבר אחד יעקוב אחרי מיקום הגלילה של דבר אחר, וכדי לעשות זאת יוצרים ScrollTimeline
ומגדירים את הגורם שמניע את הקישור לגלילה, ה-scrollSource
.
בדרך כלל אנימציה באינטרנט פועלת לפי תיקת זמן גלובלית, אבל באמצעות sectionScrollTimeline
בהתאמה אישית בזיכרון, אפשר לשנות את כל זה.
tabindicator.animate({
transform: ...,
width: ...,
}, {
duration: 1000,
fill: 'both',
timeline: sectionScrollTimeline,
}
);
לפני שנכנס לפריימים המרכזיים של האנימציה, חשוב לציין שהאובייקט שמתעד את הגלילה, tabindicator
, יתקבל אנימציה על סמך ציר זמן מותאם אישית, הגלילה של הקטע שלנו. זהו השלמת הקישור, אבל חסר המרכיב האחרון: נקודות עם מצב שמאפשרות ליצור אנימציה בין שתי נקודות, שנקראות גם 'פריימים מרכזיים'.
תמונות מפתח דינמיות
יש דרך חזקה מאוד ליצירת אנימציה באמצעות CSS דקלרטיבי טהור עם @scroll-timeline
, אבל האנימציה שבחרתי ליצור הייתה דינמית מדי. אי אפשר לבצע מעבר בין רוחב auto
, ואין אפשרות ליצור באופן דינמי מספר של נקודות מפתח על סמך אורך הצאצאים.
עם זאת, ל-JavaScript יש דרך לקבל את המידע הזה, לכן נעבור על הילדים בעצמנו ונשלוף את הערכים המחושבים בזמן הריצה:
tabindicator.animate({
transform: [...tabnavitems].map(({offsetLeft}) =>
`translateX(${offsetLeft}px)`),
width: [...tabnavitems].map(({offsetWidth}) =>
`${offsetWidth}px`)
}, {
duration: 1000,
fill: 'both',
timeline: sectionScrollTimeline,
}
);
לכל tabnavitem
, מבצעים ניתוח מבנה של המיקום offsetLeft
ומחזירים מחרוזת שמשתמשת בו כערך translateX
. כך נוצרים 4 פריים-קווים של טרנספורמציה לאנימציה. אותו הדבר נעשה לגבי רוחב, לכל אחד מהם נשאל מהו הרוחב הדינמי שלו, ואז הוא משמש כערך של keyframe.
הנה דוגמה לפלט, על סמך הגופנים וההעדפות שלי בדפדפן:
תמונות מפתח של TranslateX:
[...tabnavitems].map(({offsetLeft}) =>
`translateX(${offsetLeft}px)`)
// results in 4 array items, which represent 4 keyframe states
// ["translateX(0px)", "translateX(121px)", "translateX(238px)", "translateX(464px)"]
פריימים עיקריים של רוחב:
[...tabnavitems].map(({offsetWidth}) =>
`${offsetWidth}px`)
// results in 4 array items, which represent 4 keyframe states
// ["121px", "117px", "226px", "67px"]
לסיכום האסטרטגיה, מעכשיו תהיה אנימציה של מחוון הכרטיסייה ב-4 נקודות מפתח, בהתאם למיקום הנעילה בגלילה של סרגל הגלילה של הקטע. נקודות הצמדה יוצרות הפרדה ברורה בין נקודות ה-keyframe שלנו, ומוסיפות לתחושה הסנכרונית של האנימציה.
המשתמש מנהל את האנימציה באמצעות האינטראקציה שלו, ורואה את הרוחב והמיקום של האינדיקטור משתנים מקטע אחד לקטע הבא, תוך מעקב מושלם אחרי הגלילה.
יכול להיות שלא שמת לב, אבל אני מאוד גאה במעבר הצבע כשפריט הניווט המודגש נבחר.
ככל שהניגודיות של הפריט המודגש גבוהה יותר, הצבע האפור הבהיר יותר שלא נבחר נראה כהה יותר. שינוי צבע של טקסט הוא תופעה נפוצה, למשל כשעוברים עם העכבר מעל הטקסט או כשבוחרים אותו. אבל שינוי הצבע בזמן גלילה, בתיאום עם הסמן של הקו התחתון, הוא טריק מתקדם יותר.
כך עשיתי זאת:
tabnavitems.forEach(navitem => {
navitem.animate({
color: [...tabnavitems].map(item =>
item === navitem
? `var(--text-active-color)`
: `var(--text-color)`)
}, {
duration: 1000,
fill: 'both',
timeline: sectionScrollTimeline,
}
);
});
כל קישור לניווט בכרטיסיות צריך את האנימציה הצבעונית החדשה הזו, שתתעד את אותו ציר זמן גלילה כמו האינדיקטור של קו התחתון. אני משתמש באותה ציר זמן כמו קודם: מכיוון שהתפקיד שלו הוא להציג סימן וי בזמן גלילה, אפשר להשתמש בסימן הזה בכל סוג של אנימציה שרוצים. כמו קודם, יוצרים 4 פריימים מרכזיים בלולאה ומחזירים צבעים.
[...tabnavitems].map(item =>
item === navitem
? `var(--text-active-color)`
: `var(--text-color)`)
// results in 4 array items, which represent 4 keyframe states
// [
"var(--text-active-color)",
"var(--text-color)",
"var(--text-color)",
"var(--text-color)",
]
keyframe עם הצבע var(--text-active-color)
מדגיש את הקישור, ובמקרים אחרים הוא בצבע טקסט רגיל. הלולאה ההטמעתית שם מאפשרת לעשות זאת בצורה פשוטה יחסית, כי הלולאה החיצונית היא כל פריט ניווט, והלולאה הפנימית היא נקודות ה-keyframe האישיות של כל פריט ניווט. אני בודק אם רכיב הלולאה החיצונית זהה לרכיב הלולאה הפנימית, ומשתמש בכך כדי לדעת מתי הוא נבחר.
נהניתי מאוד לכתוב את המאמר הזה. כל כך הרבה.
שיפורים נוספים ב-JavaScript
חשוב לזכור שהעיקר של מה שאני מציג כאן פועל בלי JavaScript. עם זאת, נראה איך אפשר לשפר את התכונה כש-JS זמין.
קישורי עומק
קישורי עומק הם מונח שמקושר יותר לנייד, אבל לדעתי הכוונה של קישור העומק מתקיימת כאן באמצעות כרטיסיות, כי אפשר לשתף כתובת URL ישירות לתוכן של כרטיסייה. הדפדפן מנווט בדף למזהה שתואם ל-hash של כתובת ה-URL. גיליתי שהטיפול באירוע onload
גרם לאפקט הזה בפלטפורמות שונות.
window.onload = () => {
if (location.hash) {
tabsection.scrollLeft = document
.querySelector(location.hash)
.offsetLeft;
}
}
סנכרון של סיום הגלילה
המשתמשים שלנו לא תמיד לוחצים או משתמשים במקלדת, לפעמים הם פשוט גוללים באופן חופשי, כפי שהם אמורים לעשות. כשגליל המדור מפסיק לגלול, המקום שאליו הוא מגיע צריך להתאים למקום בסרגל הניווט העליון.
כך מחכים לסיום הגלילה:
js
tabsection.addEventListener('scroll', () => {
clearTimeout(tabsection.scrollEndTimer);
tabsection.scrollEndTimer = setTimeout(determineActiveTabSection, 100);
});
בכל פעם שמגללים את הקטעים, צריך למחוק את הזמן הקצוב לתפוגה של החלק, אם יש כזה, ולהתחיל זמן חדש. כשהגלילה של הקטעים נעצרת, לא צריך לנקות את הזמן הקצוב לתפוגה, ומומלץ להפעיל את האירוע 100 אלפיות השנייה אחרי ההפסקה. כשהאירוע מופעל, הוא קורא לפונקציה שמנסה להבין איפה המשתמש הפסיק.
const determineActiveTabSection = () => {
const i = tabsection.scrollLeft / tabsection.clientWidth;
const matchingNavItem = tabnavitems[i];
matchingNavItem && setActiveTab(matchingNavItem);
};
בהנחה שהגלילה הופסקה, חלוקת מיקום הגלילה הנוכחי ברוחב של אזור הגלילה אמורה להניב מספר שלם ולא עשרוני. לאחר מכן, אני מנסה לאחזר פריט ניווט מהמטמון שלנו באמצעות המדד המחושב הזה, ואם הוא מוצא משהו, אני שולח את ההתאמה כדי להגדיר אותה כפעילה.
const setActiveTab = tabbtn => {
tabnav
.querySelector(':scope a[active]')
.removeAttribute('active');
tabbtn.setAttribute('active', '');
tabbtn.scrollIntoView();
};
כדי להגדיר את הכרטיסייה הפעילה, קודם צריך לנקות את כל הכרטיסיות הפעילות, ואז להקצות למאפיין המצב הפעיל לפריט הניווט הנכנס. לקריאה ל-scrollIntoView()
יש אינטראקציה מעניינת עם CSS שראוי לציין.
.scroll-snap-x {
overflow: auto hidden;
overscroll-behavior-x: contain;
scroll-snap-type: x mandatory;
@media (prefers-reduced-motion: no-preference) {
scroll-behavior: smooth;
}
}
ב-CSS של הכלי להצמדה אוטומטית לגלילה אופקית, הטמענו שאילתה של מדיה שחלה על גלילה של smooth
אם המשתמש סובל תנועה. JavaScript יכול לבצע בקלות קריאות כדי לגלול רכיבים לתצוגה, ו-CSS יכול לנהל את חוויית המשתמש באופן דקלרטיבי.
לפעמים הם משלימים זה את זה בצורה נהדרת.
סיכום
עכשיו, אחרי שסיפרתי לך איך עשיתי את זה, איך היית עושה את זה? זה יוצר ארכיטקטורה מעניינת של רכיבים. מי ירצה ליצור את הגרסה הראשונה עם משבצות בסביבת הפיתוח המועדפת עליו? 🙂
נרחיב את הגישות שלנו ונלמד את כל הדרכים לפיתוח באינטרנט. יוצרים Glitch, שולחים לי את הגרסה שלכם בטוויטר ואוסיף אותה לקטע רמיקסים של הקהילה שבהמשך.
רמיקסים של הקהילה
- @devnook, @rob_dodson ו-@DasSurma עם Web Components: article.
- @jhvanderschee עם לחצנים: Codepen.