חיפוש

Scroll Snapping – שליטה בחווית הגלילה בעזרת CSS

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

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

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

CSS Scroll Snap – שימוש בסיסי

ניתן לגלול את העמוד או אלמנט כלשהו במספר דרכים: שימוש בעכבר, שימוש במקלדת, או החלקה על מסכי מגע למינהם (touch gestures).

בניגוד לסטנדרט, כלומר לחווית גלילה ליניארית בה הגלילה משקפת ישירות את תדירות המכשיר איתו ביצעת את הגלילה,  scroll snapping מאפשר ״לקפוץ״ (!Snap) לנקודה ספציפית במהלך הגלילה.

לצורך נוחות הכתיבה, הפוסט מתייחס לנקודות כנקודות קפיצה או snap points בלעז.

scroll snapping מתבצע על ידי קביעת התכונה scroll-snap-type על קונטיינר כלשהו (parent) ואת התכונה scroll-snap-align על האלמנטים בתוך אותו קונטיינר (child elements).

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

<div class="snap-container">
  <section class="child"></section>
  <section class="child"></section>
  <section class="child"></section>
</div>
.snap-container {
  scroll-snap-type: y mandatory;
}

.child {
  scroll-snap-align: start;
}

כפי שציינו, תכונות ה scroll snapping מיושמות גם על הקונטיינר וגם על האלמנטים הנמצאים בו. ניתן לומר שאופן הפעולה זהה ל Flexbox או ל-CSS Grid בהם אלמנט האב הופך להיות flex container או grid container. במקרה זה ניתן לומר שאלמנט האב הופך ל snap container.

לצורך נוחות הכתיבה, הפוסט מתייחס לאותו אלמנט אב כקונטיינר או snap container בלעז.

החלק הבא מסביר את התכונה העיקרית המתייחסת לאלמנט האב בלבד, כלומר לקונטיינר.

התכונה scroll-snap-type

scroll-snap-type קובעת כמה ״קטנוניות״ יהיו אותן נקודות קפיצה, או במילים אחרות באיזו חומרה אלו יאכפו. מעבר לערך הראשון המציין את כיווניות הגלילה, כלומר גלילה אופקית או אנכית (x או y), קיים ערך שני היכול לקבל את הערכים  mandatory או proximity.

״mandatory״ .vs ״proximity״

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

באופן כללי, ניתן לומר ש proximity נכנס לפעולה כאשר משתמש מפסיק לגלול במרחק של פיקסלים מועטים יחסית מה snap point.

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

CSS Demo: scroll-snap-type

scroll-snap-type: none;
scroll-snap-type: x mandatory;
scroll-snap-type: x proximity;
1
2
3

אם אתם תוהים כיצד עוצבו פסי הגלילה, תנו מבט בפוסט עיצוב פסי גלילה (Scrollbars) עם CSS.

התכונה scroll-snap-align

התכונה scroll-snap-align מתייחסת אך ורק לאלמנטים הנמצאים בתוך הקונטיינר (child elements). היא מאפשרת לכם לקבוע איזה חלק של האלמנט הוא שאמור לקפוץ לקונטיינר. התכונה יכולה לקבל ארבעה ערכים: none, start, center & end.

אלו יחסיים כמובן לכיווניות הגלילה. אם אתם גוללים אנכית, start יתייחס לקצה העליון של האלמנט. אם אתם גוללים אופקית, הוא יתייחס לקצה השמאלי. הערכים end & center יעקבו אחר אותו עקרון.

שימו לב! שינוי כיווניות המסמך משפיעה גם כן על תכונה זו. באתרים מימין לשמאל (RTL) הערך start יתייחס לקצה הימני.

הדוגמאות הבאות מסבירות היטב את פועלה של תכונה זו (מומלץ בדסקטופ, במובייל יהיה קשה להבין את העקרון).

Heads up: It doesn't look like your browser supports scroll snapping! Check Can I use for current browser support. Maybe try opening this CodePen in a different browser, like Chrome?

CSS Demo: scroll-snap-align

scroll-snap-align: start;
scroll-snap-align: start;
scroll-snap-align: start;
scroll-snap-align: start;
scroll-snap-align: start;
scroll-snap-align: end;
scroll-snap-align: end;
scroll-snap-align: end;
scroll-snap-align: end;
scroll-snap-align: end;
scroll-snap-align: center;
scroll-snap-align: center;
scroll-snap-align: center;
scroll-snap-align: center;
scroll-snap-align: center;
scroll-snap-align: none;
scroll-snap-align: none;
scroll-snap-align: none;
scroll-snap-align: none;
scroll-snap-align: none;

לפני סיום עם תכונה זו, חשוב לציין כי באפשרותכם לקבוע ערך שונה עבור כיוון אנכי ואופקי בצורה הבאה:

.child {
  scroll-snap-align: start end;
}

החלק הבא מציג דוגמה פרקטית יותר כדי להבין את היכולות.

דוגמה לשימוש ב scroll snap על מסך מלא

תנו מבט ב-HTML הבא:

<div class="scroll-container">
  <section>
    <h2>סקשיין 1</h2>
  </section>
  <section>
    <h2>סקשיין 2</h2>
  </section>
  <section>
    <h2>סקשיין 3</h2>
  </section>
  <section>
    <h2>סקשיין 4</h2>
  </section>
</div>

כפי שאתם כבר אמורים לדעת, scroll snapping דורש שתי תכונות CSS עיקריות: הראשונה scroll-snap-type על הקונטיינר – במקרה שלנו האלמנט עם הקלאס scroll-container. והשנייה scroll-snap-align לאלמנטים בתוך הקונטיינר (הילדים הישירים) – במקרה שלנו האלמנטים מסוג section.

מעבר לכך, יש גם להגדיר גובה לקונטיינר ולקבוע כמובן את התכונה overflow: scroll כדי לאפשר גלילה. ה CSS נראה בסגנון הבא:

.scroll-container {
  height: 100vh;
  overflow-y: scroll;
  scroll-snap-type: y mandatory;
}

section {
  height: 100vh;
  scroll-snap-align: center;
}

בדוגמה זו הכיווניות בתכונה scroll-snap-type נקבעת ל-y (כלומר גלילה אנכית) ואת הערך השני כ mandatory. זה אומר כי ברגע שהמשתמש יפסיק לגלול, מיקום הגלילה תמיד יקפוץ לנקודת הקפיצה הקרובה ביותר.

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

קביעת אנימציות או פיזיקה מדוייקת לאכיפת אותן נקודות קפיצה אינה מתקיימת בתכונה זו אלא נקבעת על ידי ה User Agent (הדפדפן) בו נעשה שימוש.

אם התוכן שלכם עלול להיות גבוה יותר מהקונטיינר (במקרה זה 100vh), השימוש ב-mandatory יכול להוות בעיה ולגרום לחלקים מהתוכן להיות חבויים מעל או מתחת לחלק הנראה לעין. במקרה כזה השתמשו ב-proximity.

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

See the Pen
Full Screen scroll-snap Demo
by Roee Yossef (@roeey)
on CodePen.

Intersection Observer API

עם CSS Scroll Snap, העמוד כבר מרגיש יותר כאפליקציית native. כדי לשפר זאת אף יותר, ניתן להוסיף אנימציות או transitions מבוססי גלילה. ניתן לעשות זאת על ידי הטמעה של אפקט פרלקס (parallax scrolling effect) או לחילופין, באמצעות Intersection Observer API.

ה-API מאפשר ליצור observer הצופה כיצד האלמנטים מצטלבים עם ה-viewport, ומפעיל פונקציה כלשהי כשאותה הצטלבות מתרחשת.

הרבה יותר יעיל מספריות המסתמכות על הקשבה תמידית ל scroll event.

ניתן ליצור observer הצופה מתי כל אחד מהסקשיינים יוצא ונכנס ל-viewport, למשל:

const sections = [...document.querySelectorAll('section')]

const options = {
  rootMargin: '0px',
  threshold: 0.25
}

const callback = (entries) => {
  entries.forEach((entry) => {
    if (entry.intersectionRatio >= 0.25) {
      target.classList.add("is-visible");
    } else {
      target.classList.remove("is-visible");
    }
  })
}

const observer = new IntersectionObserver(callback, options)

sections.forEach((section, index) => {
  observer.observe(section)
})

בדוגמה זו מופעלת פונקציית חזרה בכל פעם שסקשיין כלשהו מצטלב, או בעצם כש-25% ממנו מצטלב עם הקונטיינר (threshold). כלומר כש-25% מהסקשיין נכנס ל-viewport, הקלאס is-visible מתווסף לאותו סקשיין ומוסר כשהסקשיין אינו עומד בהגדרה זו.

כעת נותר להוסיף את האנימציה, למשל את ה-CSS הבא:

section .content {
  opacity: 0;
}

section.is-visible .content {
  opacity: 1;
  transition: opacity 1000ms;
}

והנה הדמו של התוצאה הסופית:

See the Pen
Full Screen scroll-snap Demo – Intersection Observer
by Roee Yossef (@roeey)
on CodePen.

תמיכת דפדפנים

התכונות scroll-snap-type ו-scroll-snap-align נהנות מתמיכה מצוינת בכל הדפדפנים המודרניים. כנ"ל לגבי ה-Intersection Observer.

לצורך תאימות מרבית, ניתן לעטוף את סגנונות ה-scroll snap ב-feature query (כלומר @supports) כדי לספק חווית גלישה סטנדרטית לדפדפנים ישנים.

@supports (scroll-snap-type: y mandatory) {
  .scroll-container {
    height: 100vh;
    overflow-y: scroll;
    scroll-snap-type: y mandatory;
  }

  section {
    height: 100vh;
    scroll-snap-align: center;
  }
}

שאלות נפוצות

שאלות נפוצות בנושא CSS Scroll Snap:

מה זה CSS Scroll Snap?
CSS Scroll Snap הוא סט של תכונות CSS המאפשרות לשלוט בהתנהגות הגלילה של קונטיינר כך שה-viewport "יקפוץ" לאלמנטים ילדים מוגדרים מראש. במקום גלילה ליניארית חופשית, המסך ננעל על נקודות קפיצה מוגדרות, מה שיוצר חוויה חלקה יותר דמוית אפליקציה - ללא צורך ב-JavaScript.
מה ההבדל בין mandatory ל-proximity בתכונה scroll-snap-type?
mandatory מכריח את הדפדפן תמיד לקפוץ לנקודת snap כשהגלילה נעצרת, ללא קשר למיקום. proximity קופץ רק כשהמשתמש עוצר לגלול קרוב לנקודת snap. השתמשו ב-mandatory כשהתוכן מתאים בדיוק ל-viewport, וב-proximity כשגובה התוכן אינו צפוי מראש.
מה עושה התכונה scroll-snap-align?
scroll-snap-align מוגדרת על אלמנטים ילדים בתוך ה-snap container. היא קובעת איזה חלק של האלמנט מיישר עצמו עם מיקום ה-snap של הקונטיינר. הערכים האפשריים הם start, center, end ו-none. ניתן גם לקבוע ערכים שונים לציר האנכי והאופקי (למשל start end).
האם CSS Scroll Snap נתמך בכל הדפדפנים?
כן. CSS Scroll Snap נהנה מתמיכה מצוינת בכל הדפדפנים המודרניים, כולל Chrome, Firefox, Safari ו-Edge. עבור דפדפנים ישנים יותר, ניתן לעטוף את סגנונות ה-scroll snap ב-feature query מסוג @supports כדי לספק fallback מתאים.
כיצד ניתן לשלב CSS Scroll Snap עם אנימציות?
ניתן להשתמש ב-Intersection Observer API לצד CSS Scroll Snap. ה-observer עוקב אחר כניסת ויציאת אלמנטים מה-viewport ומפעיל callback שבו ניתן להוסיף או להסיר קלאסים של CSS השולטים באנימציות, כמו fade-in או slide-in.

סיכום

CSS Scroll Snap לא מסתיים בשתי התכונות שנסקרו כאן. קיימות תכונות נוספות כמו scroll-padding ו-scroll-snap-stop ששווה לחקור בעצמכם.

עולם ה-web הולך ומתקרב לאפליקציות native ומהווה אלטרנטיבה פתוחה ונגישה לאלו. למרות שתכונות CSS אלו אינן מהוות תחליף מלא לספריות JavaScript המאפשרות שליטה רבה יותר, לתכונות ה-CSS יש יתרון משמעותי: פשטות ואמינות.

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

דיון ותגובות
3 תגובות  ]
  • אלרון 20 ינואר 2021, 6:50

    ממש מגניב! לא הכרתי את זה לפני כן. לא ייאמן מה מתאפשר לעשות עם CSS לאורך הזמן…
    אבל משום מה כשאני גולל (ממש ממש בעדינות) זה קופץ לי ב־2 פריטים ולא בפריט אחד בכל פעם. כלומר פריט 1 הופך ל2 ואז מיד ל3.

  • אילה 29 יוני 2022, 11:47

    וואו קוד מעניין ממש!!
    איך אפשר להטמיע את הקוד בעמוד שנבנה באלמנטור וורדפרס?

    • רועי יוסף 29 יוני 2022, 11:57

      היי איילה,

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

השאירו תגובה

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

Savvy WordPress Development official logo