אפקטים של מעבר בין שקפים בקרוסלה הם דרך מצוינת להוסיף עניין חזותי לקרוסלה. כשהמשתמש גולל בין השקפים, כל שקף יכול להציג אנימציה כשהוא נכנס, מתמרכז ויוצא מאזור הגלילה. לדוגמה, השקפים יכולים להופיע ולהיעלם בהדרגה, להסתובב או לשנות את הגודל שלהם. כך נוצרת חוויית משתמש דינמית ומעניינת. בניגוד לאנימציות פשוטות של כניסה/יציאה, האפקט הזה משתמש באנימציה אחת רציפה כדי לשלוט במראה של השקף בכל אזור הגלילה.
איך מטמיעים
כך יוצרים אפקטים של שקפים בקרוסלה:
יצירת רכיב גלילה: הרכיב הזה ישמש כמאגר לשקפים בקרוסלה. בדוגמה הזו נעשה שימוש ב-
overflow-x: scrollכדי לאפשר גלילה אופקית.<ul class="scroller"> <li class="entry">1</li> <li class="entry">2</li> <li class="entry">3</li> … </ul>.scroller { overflow-x: scroll; }הגדרת האנימציה: יוצרים אנימציית CSS שמגדירה את המצבים השונים של השקפים כשהם עוברים דרך אזור הגלילה. אפשר להגדיר קיפריימים לכל חלק באנימציה. לדוגמה, אפשר להגדיר מצב שבו השקפים נמצאים במרכז של אזור הגלילה על ידי הכללת פריים מפתח
50%. בדוגמה הזו, המאפייןscaleגורם לשקפים לגדול כשהם מתקרבים למרכז ולהתכווץ כשהם מתרחקים.@keyframes animate { 0% { scale: 0.5; } 50% { scale: 1; } 100% { scale: 0.5; } }מחילים את האנימציה ו
view-timeline: מצרפים את האנימציה לשקפים של הקרוסלה ומקשרים אותה ל-view-timelineשעוקב אחרי הרכיב בזמן הגלילה במאגר..scroller > * { animation: animate auto linear both; animation-timeline: view(inline); }כברירת מחדל,
view()עוקב אחרי הרכיב בצירblock. אם צריך לעקוב אחריו בצירinline, אפשר להשתמש ב-view(inline).
קוד לדוגמה
הקוד הזה מוסיף אנימציה לפריטים בקרוסלה של סרגל גלילה אופקי בזמן הגלילה, באמצעות ציר זמן אנונימי של תצוגה:
@keyframes animate {
0% {
scale: 0.5;
}
50% {
scale: 1;
}
100% {
scale: 0.5;
}
}
.scroller > * {
/* Applies the animation using an `auto` duration */
animation: animate auto linear both;
/* Sets the animation timeline to use an anonymous view progress timeline, tracking the element's progress through the scroller on the inline axis */
animation-timeline: view(inline);
}
הקוד הזה מנפיש את הפריטים בקרוסלה של סרגל גלילה אופקי בזמן הגלילה באמצעות ציר זמן של תצוגה עם שם:
@keyframes animate {
0% {
scale: 0.5;
}
50% {
scale: 1;
}
100% {
scale: 0.5;
}
}
/* This creates a named view-timeline on each carousel item. The timeline is used to drive the animation that is applied on the same element. */
.scroller > * {
/* Applies the animation using an `auto` duration */
animation: animate auto linear both;
/* Defines a named view progress timeline, tracking the element's progress through the scroller on the inline axis */
view-timeline: --item inline;
/* Sets the animation timeline to use the named view progress timeline defined above */
animation-timeline: --item;
}
שיטות מומלצות
כשמשתמשים באנימציות מבוססות-גלילה, חשוב לפעול לפי כמה שיטות מומלצות כדי להבטיח חוויה חלקה ונגישה:
- מומלץ לכלול זיהוי תכונות: לא כל הדפדפנים תומכים באנימציות מבוססות-גלילה. כדאי להשתמש ב-
@supports ((animation-timeline: view()) and (animation-range: entry))כדי לבדוק אם יש תמיכה, ולספק חלופה לדפדפנים שלא תומכים בזה.- חובה לכלול כאן את הבדיקה של
(animation-range: entry)כדי לסנן דפדפנים עם תמיכה חלקית בלבד. - אל תשתמשו בחבילה
scroll-timeline-polyfillכאסטרטגיית גיבוי, כי היא לא כוללת את כל התכונות ויש בה הרבה בעיות ידועות. - אם האנימציה נחשבת רק לקישוט, כדאי לבחור בשיפור הדרגתי (progressive enhancement) ולא לספק חלופה.
- חובה לכלול כאן את הבדיקה של
- חשוב לכבד את העדפות המשתמשים: חלק מהמשתמשים מעדיפים פחות תנועה באינטרנט. כדי להשבית או לצמצם את האנימציות למשתמשים האלה, צריך להשתמש בשאילתת המדיה
prefers-reduced-motion. - מומלץ לנסות להנפיש רק מאפייני CSS עם ביצועים גבוהים: כדי שהאנימציות יהיו חלקות ככל האפשר, כדאי להנפיש רק מאפיינים שאפשר לטפל בהם בשרשור ה-compositor של הדפדפן, כמו
transformו-opacity. הנפשה של מאפיינים אחרים כמוwidthאוheightעלולה לגרום לבעיות בביצועים. - כן: משתמשים בסדר ההצהרה הנכון. כשמשתמשים במאפיין המקוצר
animation, מצהירים עלanimation-timelineו-animation-rangeאחריו כדי למנוע מהמאפיין המקוצר לאפס את ציר הזמן.
עדיף להשתמש ב-view-timeline עם שם כשצריך להוסיף אנימציה לכמה רכיבי DOM על סמך אותו ציר זמן, או כשצריך להוסיף אנימציה לרכיבים הצאצאים של הרכיב שבו מוגדר ה-view-timeline. אם הרכיב שאתם מוסיפים לו אנימציה הוא גם הרכיב שמגדיר את view-timeline, אתם יכולים להשתמש בציר זמן אנונימי של תצוגה באמצעות view().
כשמשתמשים בפונקציה view() כדי ליצור אנימציה שמופעלת בגלילה:
- אופציונלי: ציינו במפורש את הציר שצריך לעקוב אחריו. אם אתם לא מטרגטים את הציר
blockשמוגדר כברירת מחדל (למשל, בסרגל גלילה אופקי), ציינו במפורש את הציר שצריך לעקוב אחריו באמצעותview(block)אוview(inline).
כשמשתמשים במאפיין view-timeline כדי ליצור אנימציה שמופעלת בגלילה:
- מומלץ להשתמש ב-
<dashed-ident>של CSS בשביל השם (למשלview-timeline: --my-custom-name) - אופציונלי: מציינים במפורש את הציר למעקב: כשלא מטרגטים את ציר ברירת המחדל
block(למשל, בסרגל גלילה אופקי), מציינים במפורש את הציר למעקב באמצעותview-timeline-axis. - חשוב לוודא שהיקף החיפוש פועל: כשהרכיב שמצהיר על
view-timelineהוא לא רכיב אב בעץ שטוח של הרכיב המונפש, צריך להגדיל את החשיפה של השם שלview-timelineבאמצעותtimeline-scopeברכיב אב משותף.
תמיכה בדפדפנים ושיטות חלופיות
לכן, בדרך כלל נדרשת אסטרטגיית גיבוי.
בדפדפנים שלא תומכים באנימציות מבוססות-גלילה, אפשר להשתמש ב-fallback כדי ליצור מחדש את האפקטים החזותיים. החלופות בדרך כלל נוצרות באמצעות מאזין גלילה (לאפקטים של ScrollTimeline) או IntersectionObserver API (לאפקטים של ViewTimeline).
בדפדפנים עם תמיכה מובנית באנימציות מבוססות-גלילה, תמיד צריך להשתמש בהטמעה המקורית של CSS כי היא יעילה יותר.
חשוב לזכור שלא כל אפקט אפשר ליצור מחדש באמצעות גישת החלופות.
במקרה הספציפי הזה, הסקריפט הבא מחיל את החלופה לדפדפנים שלא תומכים באנימציות מבוססות-גלילה. הוא משתמש ב-Web Animations API (Element.animate()) כדי ליצור אנימציה מושהית לכל פריט בקרוסלה. לאחר מכן, הוא מאזין לאירוע scroll ברכיב הגלילה ומעדכן את currentTime של כל אנימציה על סמך התקדמות הגלילה של הפריט בתוך רכיב הגלילה.
// Fallback for browsers that don't support scroll-driven animations
if (!CSS.supports('(animation-timeline: view()) and (animation-range: entry)')) {
const scroller = document.querySelector('.scroller');
const entries = document.querySelectorAll('.entry');
// Create a map to store animations
const animations = new Map();
entries.forEach(entry => {
const animation = entry.animate(
{
scale: ['0.5', '1', '0.5']
},
{
duration: 1, // We'll control the time ourselves
fill: 'both'
}
);
animation.pause();
animations.set(entry, animation);
});
// Update animations on scroll
const tick = () => {
const scrollerRect = scroller.getBoundingClientRect();
entries.forEach(entry => {
const animation = animations.get(entry);
if (!animation) return;
const entryRect = entry.getBoundingClientRect();
const progress = (entryRect.left + entryRect.width / 2 - scrollerRect.left) / scrollerRect.width;
animation.currentTime = progress;
});
};
scroller.addEventListener('scroll', tick);
tick();
}