אפקטים של כניסה ויציאה הם אנימציות שמופעלות כשאלמנט נכנס לאזור התצוגה או יוצא ממנו. אפשר להשתמש בזה כדי ליצור חוויות משתמש מעניינות ודינמיות. לדוגמה, אפשר להשתמש באפקט כניסה כדי להגביר בהדרגה את הבהירות של רכיב כשהוא נגלל לתוך התצוגה, או באפקט יציאה כדי להקטין אותו כשהוא נגלל אל מחוץ לתצוגה.
איך מטמיעים
כדי להוסיף אפקטים של כניסה ויציאה לרכיב, צריך לשלב כמה מאפייני CSS. הנה מדריך מפורט:
יוצרים
@keyframesנפרדים לאנימציות הכניסה והיציאה. אנימציית הכניסה תופעל כשהרכיב ייכנס לאזור התצוגה, ואנימציית היציאה תופעל כשהוא ייצא ממנו.@keyframes slide-in { from { transform: translateX(-100%); } } @keyframes slide-out { to { transform: translateX(100%); } }מצרפים את הפריימים המרכזיים של הכניסה והיציאה לאלמנט. כדי לעשות את זה, אפשר להגדיר כמה אנימציות במאפיין
animation.- נותנים לאנימציית הכניסה
animation-fill-modeשלbackwardsכדי שהיא תחיל את המצב הראשוני שלה לפני שהאנימציה מתחילה. - נותנים לאנימציית היציאה
animation-fill-modeשלforwardsכדי שהיא תשמור על המצב הסופי שלה אחרי שהאנימציה מסתיימת.
.animated-element { animation: slide-in 1s linear backwards, slide-out 1s linear forwards; }- נותנים לאנימציית הכניסה
יוצרים View Timeline ומקשרים אותו לאנימציות. ציר זמן של תצוגה הוא סוג של ציר זמן שמקושר לניראות של רכיב באזור התצוגה. אפשר ליצור פונקציה כזו באמצעות הפונקציה
view()ואז להחיל אותה על האנימציות באמצעות המאפייןanimation-timeline..animated-element { animation-timeline: view(); }כברירת מחדל,
view()עוקב אחרי הרכיב בצירblock. אם צריך לעקוב אחריו בצירinline, אפשר להשתמש ב-view(inline).הגבילו את האנימציות לטווחים
entryו-exit. המאפייןanimation-rangeמאפשר לציין באיזה חלק של ציר הזמן האנימציה צריכה לפעול.- הטווח של
entryכולל את הזמן שעובר מהרגע שהרכיב נכנס לראשונה לאזור התצוגה ועד שהוא מוצג במלואו. - הטווח
exitכולל את הזמן מהרגע שהרכיב מתחיל לצאת מאזור התצוגה ועד שהוא מוסתר לחלוטין.
.animated-element { animation-range: entry, exit; }- הטווח של
קוד לדוגמה
הקוד הזה יוצר אנימציה של הצאצאים הישירים של הגלילה באמצעות ציר זמן אנונימי של תצוגה:
@media (prefers-reduced-motion: no-preference) {
@supports ((animation-timeline: view()) and (animation-range: entry)) {
@keyframes grow {
from {
scale: 0.5;
}
}
@keyframes shrink {
to {
scale: 0.5;
}
}
.scroller > * {
animation:
grow auto linear backwards,
shrink auto linear forwards;
animation-timeline: view(inline);
animation-range: entry, exit;
}
}
}
כשהרכיבים נכנסים ליציאת הגלילה, האנימציה grow מופעלת, וכשהם יוצאים מיציאת הגלילה, האנימציה shrink מופעלת.
הקוד הבא מניב את אותה תוצאה חזותית, אבל מפעיל אנימציה על הצאצאים הישירים של הגלילה באמצעות ציר זמן של תצוגה עם שם:
@media (prefers-reduced-motion: no-preference) {
@supports ((animation-timeline: view()) and (animation-range: entry)) {
@keyframes grow {
from {
scale: 0.5;
}
}
@keyframes shrink {
to {
scale: 0.5;
}
}
.scroller > * {
view-timeline: --tl inline;
animation:
grow auto linear backwards,
shrink auto linear forwards;
animation-timeline: --tl;
animation-range: entry, exit;
}
}
}
שיטות מומלצות
כשמשתמשים באנימציות מבוססות-גלילה, חשוב לפעול לפי כמה שיטות מומלצות כדי להבטיח חוויה חלקה ונגישה:
- מומלץ לכלול זיהוי תכונות: לא כל הדפדפנים תומכים באנימציות מבוססות-גלילה. כדאי להשתמש ב-
@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אחריו כדי למנוע מהמאפיין המקוצר לאפס את ציר הזמן.
כשמשתמשים בפונקציה view() כדי ליצור אנימציה שמופעלת בגלילה:
- אופציונלי: ציינו במפורש את הציר שצריך לעקוב אחריו. אם אתם לא מטרגטים את הציר
blockשמוגדר כברירת מחדל (למשל, בסרגל גלילה אופקי), ציינו במפורש את הציר שצריך לעקוב אחריו באמצעותview(block)אוview(inline). - אם האנימציה לא מופעלת על הנושא שאחריו עוקבים, צריך להשתמש בציר זמן של תצוגה עם שם.
כשמשתמשים במאפיין view-timeline כדי ליצור אנימציה שמופעלת בגלילה:
- מומלץ להשתמש ב-
<dashed-ident>של CSS בשם. - אופציונלי: מציינים במפורש את הציר למעקב: כשלא מטרגטים את ציר ברירת המחדל
block(למשל, בסרגל גלילה אופקי), מציינים במפורש את הציר למעקב באמצעותview-timeline-axis. - חשוב לוודא שהיקף החיפוש פועל: כשהרכיב שמצהיר על
view-timelineהוא לא רכיב אב בעץ שטוח של הרכיב המונפש, צריך להגדיל את החשיפה של השם שלview-timelineבאמצעותtimeline-scopeברכיב אב משותף.
עדיף להשתמש ב-view-timeline עם שם כשצריך להוסיף אנימציה לכמה רכיבים או צאצאים של הנושא שאחריו עוקבים.
תמיכה בדפדפנים ושיטות חלופיות
לכן, בדרך כלל נדרשת אסטרטגיית גיבוי.
בדפדפנים שלא תומכים באנימציות מבוססות-גלילה, אפשר להשתמש ב-fallback כדי ליצור מחדש את האפקטים החזותיים. החלופות בדרך כלל נוצרות באמצעות מאזין גלילה (לאפקטים של ScrollTimeline) או IntersectionObserver API (לאפקטים של ViewTimeline).
בדפדפנים עם תמיכה מובנית באנימציות מבוססות-גלילה, תמיד צריך להשתמש בהטמעה המקורית של CSS כי היא יעילה יותר.
חשוב לזכור שלא כל אפקט אפשר ליצור מחדש באמצעות גישת החלופות.
במקרה הספציפי הזה, הסקריפט הבא מחיל את החלופה לדפדפנים שלא תומכים באנימציות מבוססות-גלילה. הוא משתמש ב-IntersectionObserver כדי לעקוב אחרי החשיפה של הרכיב .wrapper ומעדכן את המאפיין transform של השכבות על סמך מיקום הגלילה.
<script>
if (!CSS.supports('(animation-timeline: view()) and (animation-range: entry)')) {
const observer = new IntersectionObserver(
(entries) => {
for (const entry of entries) {
// This matches the effect as defined in the CSS example above.
// Customize this further if needed.
entry.target.style.scale = 0.5 + entry.intersectionRatio * 0.5;
}
},
{
threshold: Array.from({ length: 101 }, (_, i) => i / 100),
}
);
document.querySelectorAll('.scroller > *').forEach((el) => {
observer.observe(el);
});
}
</script>