תחילת העבודה עם אנימציות שמונעות גלילה ב-CSS

1. לפני שמתחילים

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

חדש ב-Chrome 115: תמיכה בקבוצה של כיתות JavaScript ומאפייני CSS שמאפשרים ליצור בקלות אנימציות מוצגות (declarative) שמבוססות על גלילה. ממשקי ה-API החדשים האלה פועלים בשילוב עם ממשקי ה-API הקיימים של Web Animations ו-CSS Animations.

בשיעור הזה תלמדו איך ליצור אנימציות שמתבססות על גלילה באמצעות CSS. בסיום הקודלאב הזה, תתרגלו את מאפייני ה-CSS הרבים שהתכונה המלהיבה הזו מאפשרת להשתמש בהם, כמו scroll-timeline, ‏view-timeline, ‏animation-timeline ו-animation-range.

מה תלמדו

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

מה נדרש

אחד משילובי המכשירים הבאים:

  • גרסה עדכנית של Chrome (115 ואילך) ב-ChromeOS, ב-macOS או ב-Windows, עם הדגל 'תכונות ניסיוניות של פלטפורמת אינטרנט' מוגדר כ'מופעל'.
  • הבנה בסיסית של HTML
  • הבנה בסיסית של CSS, במיוחד אנימציות ב-CSS

2. להגדרה

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

  1. פותחים כרטיסייה חדשה בדפדפן ועוברים לכתובת https://github.com/googlechromelabs/io23-scroll-driven-animations-codelab.
  2. משכפלים את המאגר.
  3. פותחים את הקוד בסביבת הפיתוח המשולבת (IDE) המועדפת.
  4. מריצים את הפקודה npm install כדי להתקין את יחסי התלות.
  5. מריצים את npm start ונכנסים לכתובת http://localhost:3000/.
  6. לחלופין, אם npm לא מותקן, פותחים את הקובץ src/index.html ב-Chrome.

3. מידע על צירי זמן של אנימציה

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

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

  • גלילה בציר הזמן של ההתקדמות
  • הצגת ציר הזמן של ההתקדמות

ב-CSS, אפשר לצרף את צירי הזמן האלה לאנימציה באמצעות המאפיין animation-timeline. כדאי לעיין בהסבר על לוחות הזמנים החדשים ועל ההבדלים ביניהם.

גלילה בציר הזמן של ההתקדמות

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

מיקום הגלילה בהתחלה מייצג 0% התקדמות ומיקום הגלילה בסיום מייצג 100% התקדמות. בתצוגה החזותית הבאה, שימו לב שההתקדמות עולה מ-0% ל-100% כשגוללים למטה בפס ההזזה.

הצגת ציר הזמן של ההתקדמות

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

ציר הזמן של התקדמות הצפייה מתחיל מהרגע שבו הנושא מתחיל לחפוף לפס ההזזה ומסתיים כשהנושא מפסיק לחפוף לפס ההזזה. בתרשים הוויזואלי הבא, שימו לב שההתקדמות מתחילה להיספר מ-0% כשהנושא נכנס למאגר הגלילה ומגיעה ל-100% כשהנושא יוצא ממאגר הגלילה.

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

אפשר גם לקשר אותו לחלק ספציפי ב'ציר הזמן של התקדמות הצפייה' על ידי ציון הטווח שאליו הוא צריך להיות מצורף. לדוגמה, רק כשהנושא נכנס לגלילה. בתרשים הוויזואלי הבא, ההתקדמות מתחילה להיספר מ-0% כשהנושא נכנס למאגר הגלילה, אבל כבר מגיעה ל-100% מהרגע שהוא חוצה אותו לגמרי.

הטווחים האפשריים של ציר הזמן של הצפייה שאפשר לטרגט הם cover,‏ contain,‏ entry,‏ exit,‏ entry-crossing ו-exit-crossing. ההסבר על הטווחים האלה מופיע בהמשך הקודלאב, אבל אם אתם לא יכולים לחכות, תוכלו להשתמש בכלי שנמצא בכתובת https://goo.gle/view-timeline-range-tool כדי לראות מה כל טווח מייצג.

4. יצירת אפקט רקע של תזוזת חזותית (Parallax)

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

כדי להטמיע את האפשרות הזו, צריך לבצע שני שלבים:

  1. ליצור אנימציה שמזיזה את מיקום תמונת הרקע.
  2. מקשרים את האנימציה להתקדמות הגלילה במסמך.

יצירת האנימציה

  1. כדי ליצור את האנימציה, משתמשים בקבוצה רגילה של נקודות מפתח. מעבירים את מיקום הרקע מ-0% אנכית ל-100%:

src/styles.css

@keyframes move-background {
  from {
    background-position: 50% 0%;
  }
  to {
    background-position: 50% 100%;
  }
}
  1. עכשיו מצרפים את נקודות ה-keyframe האלה לרכיב הגוף:

src/styles.css

body {
  animation: 1s linear move-background;
}

הקוד הזה מוסיף את האנימציה move-background לרכיב body. המאפיין animation-duration מוגדר לשנייה אחת, והוא משתמש בתנועה linear.

הדרך הקלה ביותר ליצור ציר זמן של התקדמות גלילה היא להשתמש בפונקציה scroll(). הפעולה הזו יוצרת ציר זמן אנונימי של התקדמות גלילה, שאפשר להגדיר כערך של המאפיין animation-timeline.

הפונקציה scroll() מקבלת את הארגומנטים <scroller> ו-<axis>.

הערכים הקבילים לארגומנט <scroller> הם:

  • nearest. המערכת משתמשת בקונטיינר הגלילה הקרוב ביותר של האב (ברירת המחדל).
  • root. נעשה שימוש בחלון התצוגה של המסמך כקונטיינר הגלילה.
  • self. הרכיב עצמו משמש כקונטיינר לגלילה.

הערכים הקבילים לארגומנט <axis> הם:

  • block. שימוש במדד ההתקדמות לאורך ציר הבלוק של קונטיינר הגלילה (ברירת המחדל).
  • inline. נעשה שימוש במדד ההתקדמות לאורך הציר של קונטיינר הגלילה.
  • y. נעשה שימוש במדד ההתקדמות לאורך ציר ה-Y של מאגר הגלילה.
  • x. ההתקדמות נמדדת בציר ה-X של קונטיינר הגלילה.

כדי לקשר את האנימציה לפס ההזזה ברמה הבסיסית בציר הבלוק, הערכים שצריך להעביר אל scroll() הם root ו-block. בסך הכול, הערך הוא scroll(root block).

  • מגדירים את הערך scroll(root block) למאפיין animation-timeline בגוף ההודעה.
  • בנוסף, מכיוון שאין הגיון ב-animation-duration שמוצג בשניות, צריך להגדיר את משך הזמן כ-auto. אם לא מציינים ערך ל-animation-duration, ברירת המחדל היא auto.

src/styles.css

body {
  animation: linear move-background;
  animation-duration: auto;
  animation-timeline: scroll(root block);
}

מכיוון שגליל השורש הוא גם גליל ההורה הקרוב ביותר לרכיב הגוף, אפשר גם להשתמש בערך nearest:

src/styles.css

body {
  animation: linear move-background;
  animation-duration: auto;
  animation-timeline: scroll(nearest block);
}

מאחר ש-nearest ו-block הם ערכי ברירת המחדל, אפשר אפילו להשמיט אותם. במקרה כזה, אפשר לפשט את הקוד כך:

src/styles.css

body {
  animation: linear move-background;
  animation-duration: auto;
  animation-timeline: scroll();
}

אימות השינויים

אם הכל הלך כשורה, זה מה שיופיע:

אם לא, צריך לבדוק את ההסתעפות solution-step-1 של הקוד.

5. יצירת סרגל התקדמות לגלריה של התמונות

בדף מוצגת קרוסלה אופקית שצריך להוסיף לה סרגל התקדמות כדי לציין איזו תמונה מוצגת כרגע.

ה-Markup של הקרוסלה נראה כך:

src/index.html

<div class="gallery">
  <div class="gallery__scrollcontainer" style="--num-images: 3;">
    <div class="gallery__progress"></div>
    <div class="gallery__entry">
      ...
    </div>
    <div class="gallery__entry">
      ...
    </div>
    <div class="gallery__entry">
      ...
    </div>
  </div>
</div>

מפתחות ה-keyframe של סרגל ההתקדמות כבר נמצאים במקום ונראים כך:

src/styles.css

@keyframes adjust-progress {
  from {
    transform: scaleX(calc(1 / var(--num-images)));
  }
  to {
    transform: scaleX(1);
  }
}

צריך לצרף את האנימציה הזו לקובץ .רכיב gallery__progress עם ציר זמן של התקדמות הגלילה. כפי שצוין בשלב הקודם, אפשר לעשות זאת על ידי יצירת ציר זמן אנונימי של התקדמות גלילה באמצעות הפונקציה scroll():

src/styles.css

.gallery__progress {
  animation: linear adjust-progress;
  animation-duration: auto;
  animation-timeline: scroll(nearest inline);
}

נראה שקוד הקוד הזה אמור לפעול, אבל הוא לא פועל בגלל האופן שבו פועלות בדיקות אוטומטיות של מאגרי גלילה באמצעות nearest. כשמחפשים את סרגל ההזזה הקרוב ביותר, הרכיב מתייחס רק לרכיבים שיכולים להשפיע על המיקום שלו. מכיוון ש-.gallery__progress ממוקם באופן מוחלט, רכיב ההורה הראשון שיקבע את המיקום שלו הוא רכיב .gallery, כי הוחל עליו position: relative. כלומר, רכיב .gallery__scrollcontainer – שהוא סרגל הגלילה שצריך לטרגט – לא נלקח בחשבון כלל במהלך החיפוש האוטומטי הזה.

כדי לעקוף את הבעיה הזו, יוצרים ציר זמן של התקדמות גלילה בשם מסוים ברכיב .gallery__scrollcontainer ומקשרים אליו את .gallery__progress באמצעות השם הזה.

כדי ליצור ציר זמן של התקדמות גלילה עם שם ברכיב, מגדירים את מאפיין ה-CSS scroll-timeline-name במיכל הגלילה לערך הרצוי. הערך חייב להתחיל ב---.

מכיוון שהגלילה בגלריה היא אופקית, צריך להגדיר גם את המאפיין scroll-timeline-axis. הערכים המותרים זהים לארגומנט <axis> של scroll().

לבסוף, כדי לקשר את האנימציה לציר הזמן של התקדמות הגלילה, מגדירים את המאפיין animation-timeline ברכיב שרוצים להוסיף לו אנימציה לאותו ערך של המזהה שמשמש את scroll-timeline-name.

  • משנים את הקובץ styles.css כך שיכלול את הפרטים הבאים:

src/styles.css

.gallery__scrollcontainer {
  /* Create the gallery-is-scrolling timeline */
  scroll-timeline-name: --gallery-is-scrolling;
  scroll-timeline-axis: inline;
}

.gallery__progress {
  animation: linear adjust-progress;
  animation-duration: auto;
  /* Set gallery-is-scrolling as the timeline */
  animation-timeline: --gallery-is-scrolling;
}

אימות השינויים

אם הכל הלך כשורה, זה מה שיופיע:

אם לא, צריך לבדוק את ההסתעפות solution-step-2 של הקוד.

6. אנימציה של התמונות בגלריה כשהן נכנסות ויוצאות מחלון הגלילה

הגדרת ציר זמן אנונימי של התקדמות הצפיות

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

כדי ליצור ציר זמן של התקדמות הצפייה, אפשר להשתמש בפונקציה view(). הארגומנטים הקבילים שלו הם <axis> ו-<view-timeline-inset>.

  • הערך של <axis> זהה לערך של ציר הזמן של התקדמות הגלילה, והוא קובע איזה ציר יתבצע אחריו מעקב.
  • בעזרת <view-timeline-inset> אפשר לציין אופסט (חיובי או שלילי) כדי לשנות את הגבולות של המצב שבו רכיב נחשב כגלוי או לא גלוי.
  • נקודות ה-keyframe כבר נמצאות במקום, ולכן צריך רק לצרף אותן. כדי לעשות זאת, יוצרים ציר זמן של התקדמות הצפייה בכל רכיב .gallery__entry.

src/styles.css

@keyframes animate-in {
  from {
    opacity: 0;
    clip-path: inset(50% 0% 50% 0%);
  }
  to {
    opacity: 1;
    clip-path: inset(0% 0% 0% 0%);
  }
}

.gallery__entry {
  animation: linear animate-in;
  animation-duration: auto;
  animation-timeline: view(inline);
}

הגבלת הטווח של ציר הזמן של התקדמות הצפייה

אם שומרים את קובץ ה-CSS וטעונים את הדף, רואים את הרכיבים מופיעים בהדרגה, אבל משהו נראה לא בסדר. הם מתחילים עם ערך אטימות 0 כשהם לא גלויים לחלוטין, ומסיימים עם ערך אטימות 1 רק כשהם יוצאים לגמרי מהמסך.

הסיבה לכך היא שטווח ברירת המחדל של ציר הזמן של התקדמות הצפייה הוא הטווח המלא. זה נקרא טווח cover.

  1. כדי לטרגט רק את טווח entry של הנושא, משתמשים במאפיין ה-CSS animation-range כדי להגביל את הזמנים שבהם האנימציה צריכה לפעול.

src/styles.css

.gallery__entry {
  animation: linear fade-in;
  animation-duration: auto;
  animation-timeline: view(inline);
  animation-range: entry 0% entry 100%;
}

האנימציה פועלת עכשיו מ-entry 0% (הנושא עומד להיכנס לפס ההזזה) עד entry 100% (הנושא נכנס לפס ההזזה במלואו).

אלה טווחי ציר הזמן האפשריים בתצוגה:

  • cover. מייצג את כל טווח ציר הזמן של התקדמות הצפייה.
  • entry. מייצג את הטווח שבו התיבה של חשבון המשתמש נכנסת לטווח החשיפה של התקדמות הצפייה.
  • exit. מייצג את הטווח שבו התיבה של חשבון המשתמש יוצאת מטווח החשיפה של התקדמות הצפייה.
  • entry-crossing. מייצג את הטווח שבו התיבה של חשבון המשתמש חוצה את קצה הגבול.
  • exit-crossing. מייצג את הטווח שבו התיבה של חשבון המשתמש חוצה את קצה הגבול של ההתחלה.
  • contain. מייצג את הטווח שבו התיבה של חשבון המשתמש נכללת באופן מלא בטווח החשיפה של התקדמות התצוגה בחלונית הגלילה, או מכסה אותו באופן מלא. זה תלוי אם הנושא גבוה או נמוך מהגלילה.

אפשר להשתמש בכלי שנמצא בכתובת https://goo.gle/view-timeline-range-tool כדי לראות מה כל טווח מייצג ואיך האחוזים משפיעים על מיקומי ההתחלה והסיום.

  1. מכיוון שטווחי ההתחלה והסיום זהים כאן ונעשה שימוש בהיסטים שמוגדרים כברירת מחדל, אפשר לפשט את animation-range לשם אחד של טווח אנימציה:

src/styles.css

.gallery__entry {
  animation: linear animate-in;
  animation-duration: auto;
  animation-timeline: view(inline);
  animation-range: entry;
}
  • כדי שהתמונות ייעלמו בהדרגה כשהן יוצאות מהפס ההזזה, אפשר לבצע את אותן פעולות כמו באנימציה של כניסה, אבל לטרגט טווח אחר.

src/styles.css

@keyframes animate-out {
  from {
    opacity: 1;
    clip-path: inset(0% 0% 0% 0%);
  }
  to {
    opacity: 0;
    clip-path: inset(50% 0% 50% 0%);
  }
}

.gallery__entry {
  animation: linear animate-in, linear animate-out;
  animation-duration: auto;
  animation-timeline: view(inline);
  animation-range: entry, exit;
}

נקודות ה-keyframe של animate-in יחולו על טווח entry ונקודות ה-keyframe של animate-out יחולו על טווח exit.

אימות השינויים

אם הכל הלך כשורה, זה מה שיופיע:

אם לא, צריך לבדוק את ההסתעפות solution-step-3 של הקוד.

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

המקרה של קבוצה אחת של נקודות מפתח

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

הצורה של נקודות ה-keyframe נראית כך:

@keyframes keyframes-name {
  range-name range-offset {
    ...
  }
  range-name range-offset {
    ...
  }
}
  1. משלבים את הפריימים המרכזיים של ההתחלה והסיום כך:

src/styles.css

@keyframes animate-in-and-out {
  entry 0% {
    opacity: 0;
    clip-path: inset(50% 0% 50% 0%);
  }
  entry 90% {
    opacity: 1;
    clip-path: inset(0% 0% 0% 0%);
  }

  exit 10% {
    opacity: 1;
    clip-path: inset(0% 0% 0% 0%);
  }
  exit 100% {
    opacity: 0;
    clip-path: inset(50% 0% 50% 0%);
  }
}
  1. כשפרטי הטווח נמצאים בפריימים המרכזיים, כבר לא צריך לציין את animation-range בנפרד. מחברים את נקודות ה-keyframe כנכס animation.

src/styles.css

.gallery__entry {
  animation: linear animate-in-and-out both;
  animation-duration: auto;
  animation-timeline: view(inline);
}

אימות השינויים

אם הכל הלך כשורה, התוצאה אמורה להיות זהה לתוצאה מהשלב הקודם. אם לא, צריך לבדוק את ההסתעפות solution-step-4 של הקוד.

8. מעולה!

סיימתם את ה-codelab הזה ועכשיו אתם יודעים איך ליצור צירי זמן של התקדמות גלילה וצירי זמן של התקדמות צפייה ב-CSS.

מידע נוסף

מקורות: