בפרק 4 למדתם GSAP — tweens, timelines, stagger, SplitText. עכשיו הגיע הזמן לשאלה שכל Vibe Coder שואל: "איך גורמים לאנימציה לקרות כשהמשתמש גולל לשם?" התשובה היא ScrollTrigger — הפלאגין הכי פופולרי של GSAP, שהפך לסטנדרט התעשייה לאנימציות scroll. בפרק הזה תלמדו הכל: מ-scroll-triggered פשוט (האנימציה מתחילה כשגוללים) דרך scroll-driven (האנימציה צמודה לגלילה פיקסל-בפיקסל) ועד pin, parallax, horizontal scroll, snap ו-batch. תלמדו גם מתי להשתמש ב-CSS scroll-driven animations (החדשות) ומתי צריך ScrollTrigger. תצאו מהפרק עם חוויית scroll שלמה — הדבר שמפריד בין אתר "סטטי" לבין אתר שמרגיש כמו סיפור אינטראקטיבי.
בפרק 4 בניתם timeline שלם ל-hero section עם GSAP. בפרק הזה תקחו את זה צעד קדימה: תחברו את האנימציות שלכם ל-scroll. ה-hero שבניתם יקבל pin — כדי שהוא "ייתקע" על המסך בזמן שהאנימציה רצה. תוסיפו parallax רקע, תבנו section של horizontal scroll עם snap, ותסגרו עם batch animation לגלריית עבודות. בפרק 6 תלמדו motion design — איך לתכנן תנועה שמספרת סיפור, ותשתמשו ב-ScrollTrigger כדי לממש את זה.
| מונח (English) | תרגום | הגדרה |
|---|---|---|
| ScrollTrigger | טריגר גלילה | פלאגין של GSAP שמחבר אנימציות לגלילת הדף — הפלאגין הכי פופולרי במערכת GSAP. חינם מאז 2020 |
| trigger | אלמנט מפעיל | האלמנט ב-DOM שמפעיל את ה-ScrollTrigger — כשהוא נכנס ל-viewport (או לנקודה ספציפית), האנימציה מתחילה |
| start | נקודת התחלה | מגדיר מתי ה-ScrollTrigger מתחיל. הפורמט: "trigger-point viewport-point", למשל "top center" = כשהחלק העליון של ה-trigger מגיע למרכז ה-viewport |
| end | נקודת סיום | מגדיר מתי ה-ScrollTrigger מסתיים. למשל "bottom top" = כשהחלק התחתון של ה-trigger יוצא מלמעלה של ה-viewport |
| scrub | צמידות לגלילה | כשמופעל — האנימציה צמודה לעמדת הגלילה. scrub: true = מיידי. scrub: 1 = החלקה של שנייה. ההבדל בין scroll-triggered ל-scroll-driven |
| pin | נעיצה | "מנעיל" אלמנט במקומו בזמן שהגלילה ממשיכה — האלמנט נשאר fixed עד שה-ScrollTrigger מסתיים. חיוני ל-storytelling sections |
| parallax | פרלקס | אפקט שבו שכבות שונות נעות במהירויות שונות בזמן גלילה — יוצר תחושת עומק תלת-ממדית. שכבת רקע נעה לאט, תוכן רגיל, אלמנטים צפים נעים מהר |
| snap | הצמדה | הגלילה "נצמדת" לנקודות מוגדרות — אחרי שהמשתמש מפסיק לגלול, הדף "קופץ" לנקודת ה-snap הקרובה ביותר. שימושי ל-fullpage sections |
| batch | אצווה | ScrollTrigger.batch() — מפעיל אנימציה על קבוצת אלמנטים שנכנסים ל-viewport ביחד. מושלם לגלריות, רשימות, וגרידים |
| horizontal scroll | גלילה אופקית | אפקט שבו גלילה אנכית רגילה מתורגמת לתנועה אופקית של container — יוצר חוויית "סליידר" שנשלט על ידי גלילה |
| scroll-driven animation | אנימציה מונעת גלילה | אנימציה שהתקדמותה צמודה לעמדת הגלילה — כל פיקסל של גלילה מזיז את האנימציה. ב-GSAP: scrub מופעל. ב-CSS: scroll-timeline |
| scroll-triggered animation | אנימציה מופעלת גלילה | אנימציה שמתחילה כשגוללים לנקודה מסוימת — אבל אחרי שהתחילה היא רצה בקצב שלה (לא צמודה לגלילה). ב-GSAP: בלי scrub |
| markers | סמנים | markers: true — מציג סמנים ויזואליים על הדף שמראים בדיוק איפה ה-start ו-end של כל ScrollTrigger. כלי debug חיוני — תמיד תתחילו עם markers |
ScrollTrigger הוא פלאגין של GSAP שמחבר אנימציות לגלילת הדף. הוא הפלאגין הכי פופולרי של GSAP — וזה לא מפתיע, כי אנימציות scroll הן הדבר הראשון שמבדיל בין אתר "סטטי" לבין אתר שמרגיש כמו חוויה. כשאתם גוללים באתר של Apple, Stripe, או Linear ורואים אלמנטים שנכנסים, טקסט שנחשף, ותמונות שצפות — ברוב המקרים זה ScrollTrigger.
כמה עובדות חשובות על ScrollTrigger:
מתי ScrollTrigger קריטי ל-Vibe Coder? כל פעם שאתם בונים landing page, portfolio, product page, או כל דף שצריך "לספר סיפור" — ScrollTrigger הוא הכלי. בלי scroll animations, המשתמש רואה את כל התוכן בבת אחת — אין הפתעה, אין גילוי, אין תחושת "מסע". עם ScrollTrigger, כל section מתגלה בזמן הנכון, כל אלמנט נכנס בדרך שמושכת תשומת לב, וכל גלילה מרגישה כמו צעד קדימה בסיפור. זה ההבדל בין אתר שמשתמשים סוגרים אחרי 3 שניות לבין אתר שגוללים עד הסוף.
ככה נראה ScrollTrigger בצורה הכי בסיסית:
// שלב 1: רישום הפלאגין (פעם אחת, בתחילת הקוד)
gsap.registerPlugin(ScrollTrigger);
// שלב 2: אנימציה עם ScrollTrigger
gsap.to(".hero-title", {
opacity: 1,
y: 0,
duration: 1,
scrollTrigger: ".hero-title" // הצורה הפשוטה ביותר
});
// מה קורה: כש-.hero-title נכנס ל-viewport, האנימציה מתחילה
זה הכל! שורה אחת של scrollTrigger — ופתאום האנימציה מופעלת בגלילה במקום בטעינת הדף. אבל הצורה הפשוטה הזו היא רק קצה הקרחון. השליטה האמיתית מתחילה כשמשתמשים באובייקט ScrollTrigger מלא:
gsap.to(".hero-title", {
opacity: 1,
y: 0,
duration: 1,
scrollTrigger: {
trigger: ".hero-title", // האלמנט שמפעיל
start: "top 80%", // מתי להתחיל
end: "top 20%", // מתי לסיים
scrub: true, // צמוד לגלילה
markers: true, // סמני debug (הסירו בפרודקשן!)
pin: false, // אל תנעץ
toggleActions: "play none none reverse"
}
});
הכוח של ScrollTrigger הוא בשליטה הזו. כל property שנלמד בפרק הזה — trigger, start, end, scrub, pin, snap — נותן לכם שכבה נוספת של שליטה. בסוף הפרק, תדעו לבנות חוויות scroll שלמות שמרגישות כמו סיפור אינטראקטיבי.
טעינת ScrollTrigger — שתי דרכים:
// דרך 1: CDN (הכי מהיר להתחלה)
<script src="https://cdn.jsdelivr.net/npm/gsap@3/dist/gsap.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/gsap@3/dist/ScrollTrigger.min.js"></script>
// דרך 2: npm (לפרויקטים עם bundler)
// npm install gsap
import { gsap } from "gsap";
import { ScrollTrigger } from "gsap/ScrollTrigger";
gsap.registerPlugin(ScrollTrigger);
// חשוב! registerPlugin חייב להיקרא לפני כל שימוש ב-ScrollTrigger
gsap.registerPlugin(ScrollTrigger); gsap.from(".box", { opacity: 0, y: 50, duration: 1, scrollTrigger: ".box" });זה ההבדל הכי חשוב בפרק הזה. הוא ההבדל שרוב המתחילים לא מבינים, ושגורם לתסכול כשהאנימציה לא עובדת "כמו שציפיתם". שתי גישות, שתי תחושות שונות לחלוטין:
scroll-triggered (מופעלת גלילה): האנימציה מתחילה כשגוללים לנקודה מסוימת — אבל אחרי שהתחילה, היא רצה בקצב שלה. ממש כמו ללחוץ על כפתור "play". לא משנה אם תגללו מהר, לאט, או תעצרו — האנימציה תסיים בזמן שלה. זו התנהגות ברירת המחדל של ScrollTrigger.
scroll-driven (מונעת גלילה): האנימציה צמודה לעמדת הגלילה. כל פיקסל של גלילה מזיז את האנימציה קדימה. עוצרים לגלול? האנימציה עוצרת. גוללים אחורה? האנימציה חוזרת אחורה. כמו סרטון שמנגנים עם העכבר. ב-GSAP: scrub מופעל.
// scroll-TRIGGERED — האנימציה מתחילה וממשיכה לבד
gsap.to(".card", {
x: 200,
rotation: 360,
duration: 2,
scrollTrigger: {
trigger: ".card",
start: "top 80%",
// אין scrub! האנימציה רצה 2 שניות אחרי trigger
}
});
// scroll-DRIVEN — האנימציה צמודה לגלילה
gsap.to(".card", {
x: 200,
rotation: 360,
scrollTrigger: {
trigger: ".card",
start: "top 80%",
end: "bottom 20%",
scrub: true, // ← זה מה שהופך אותה ל-scroll-driven
// duration מתעלם! הזמן נקבע לפי מהירות הגלילה
}
});
| קריטריון | scroll-triggered (בלי scrub) | scroll-driven (עם scrub) |
|---|---|---|
| תחושה | אנימציה "קופצת" — רואים תנועה ברגע ספציפי | אנימציה "זורמת" — צמודה לגלילה כמו סרט |
| שליטת משתמש | אין — אחרי ההפעלה, האנימציה רצה לבד | מלאה — המשתמש שולט בקצב, כיוון, ועצירה |
| שימוש עיקרי | כניסת אלמנטים (fade in, slide up), text reveals, notifications | progress bars, storytelling, parallax, horizontal scroll |
| duration | נקבע ב-duration property (למשל 1 שנייה) | נקבע על ידי המרחק בין start ל-end |
| easing | עובד רגיל — power2.out, elastic, bounce | בדרך כלל "none" — כי הגלילה עצמה היא ה-easing |
| ביצועים | קל — האנימציה רצה פעם אחת | כבד יותר — מעדכן כל frame בזמן גלילה |
| toggleActions | רלוונטי! שולט ב-play/reverse/restart/reset | לא רלוונטי — scrub דורס את toggleActions |
| Vibe Coder tip | "כשהמשתמש מגיע לכאן — תפתיע אותו" | "תן למשתמש לשלוט באנימציה עם הגלילה" |
הכלל הפשוט: אם אתם רוצים שהמשתמש ירגיש שהוא "שולט" באנימציה — scrub. אם אתם רוצים שהאנימציה "תקפוץ" ברגע ספציפי — בלי scrub. רוב האתרים משתמשים בשילוב של שניהם: כניסת אלמנטים ב-scroll-triggered, ואפקטים כמו parallax ו-progress bars ב-scroll-driven.
אם תוסיפו scrub: true לאנימציה עם ease: "bounce.out" — תקבלו אפקט מוזר שנראה "שבור". הסיבה: bounce easing מניח שהאנימציה רצה בזמן קבוע, אבל עם scrub הזמן נקבע על ידי הגלילה. כלל אצבע: scrub + bounce/elastic = בעיה. scrub + linear/power1 = מצוין. אם בכל זאת רוצים bounce עם scroll — השתמשו ב-scroll-triggered (בלי scrub) עם toggleActions שמפעיל את האנימציה כש-trigger מגיע.
gsap.to(".box-a", { x: 300, duration: 1, scrollTrigger: { trigger: ".box-a", start: "top 80%" } });gsap.to(".box-b", { x: 300, scrollTrigger: { trigger: ".box-b", start: "top 80%", end: "top 20%", scrub: true } });שלושת ה-properties הכי חשובים ב-ScrollTrigger הם trigger, start, ו-end. הם קובעים מה מפעיל את האנימציה, מתי היא מתחילה, ומתי היא מסתיימת. בלי להבין אותם — תהיו תקועים עם ניחושים. עם markers:true — תראו בדיוק מה קורה.
ה-trigger הוא האלמנט שה-ScrollTrigger "עוקב" אחריו. שימו לב: הוא לא חייב להיות אותו אלמנט שמקבל את האנימציה! אפשר להפעיל אנימציה על element-A כשגוללים ל-element-B:
// trigger ≠ target — זה חוקי ונפוץ!
gsap.to(".navbar", { // ← האלמנט שמקבל אנימציה
backgroundColor: "#000",
duration: 0.3,
scrollTrigger: {
trigger: ".hero", // ← האלמנט שמפעיל
start: "bottom top", // כשתחתית ה-hero יוצאת מלמעלה
toggleActions: "play none none reverse"
}
});
// המשמעות: כשגוללים מעבר ל-hero, ה-navbar הופך שחור
start ו-end מקבלים שני ערכים: "trigger-point viewport-point". הערך הראשון הוא נקודה על ה-trigger element, והשני הוא נקודה על ה-viewport. כשהשני מגיע לראשון — ה-ScrollTrigger מופעל.
scrollTrigger: {
trigger: ".section",
start: "top center", // כשה-top של .section מגיע ל-center של ה-viewport
end: "bottom top", // כשה-bottom של .section מגיע ל-top של ה-viewport
}
הערכים האפשריים:
| ערך | על trigger element | על viewport |
|---|---|---|
| "top" | הקצה העליון של ה-trigger | הקצה העליון של המסך |
| "center" | אמצע ה-trigger | אמצע המסך |
| "bottom" | הקצה התחתון של ה-trigger | הקצה התחתון של המסך |
| "80%" | 80% מלמעלה של ה-trigger | 80% מלמעלה של המסך |
| "+=300" | 300px מתחת ל-top של trigger | 300px מתחת ל-top של viewport |
הדוגמאות הכי נפוצות:
// 1. "top 80%" — הכי נפוץ ל-scroll-triggered
// האנימציה מתחילה כשה-trigger כמעט במסך (80% מלמעלה)
start: "top 80%"
// 2. "top center" — כשה-trigger באמצע המסך
start: "top center"
// 3. "top top" — כשה-trigger בדיוק בראש המסך
start: "top top"
// 4. "center center" — כשמרכז ה-trigger במרכז המסך
start: "center center"
// 5. "top bottom" — כשה-trigger נכנס ל-viewport (מלמטה)
start: "top bottom" // ברירת המחדל!
// 6. עם פיקסלים — שליטה מדויקת
start: "top 80%",
end: "+=500" // 500px אחרי ה-start
markers: true מוסיף סמנים ויזואליים על הדף — קווים ירוקים (start) ואדומים (end) שמראים בדיוק איפה ה-ScrollTrigger מופעל ומסתיים. זה הכלי הכי חשוב ב-ScrollTrigger. תמיד, תמיד, תמיד תתחילו עם markers: true. רק אחרי שהכל עובד — תסירו אותם.
scrollTrigger: {
trigger: ".section",
start: "top 80%",
end: "bottom 20%",
markers: true, // ← יציג scroller-start, scroller-end, start, end
scrub: true
}
ה-markers מראים 4 קווים:
כשהקו הירוק על ה-trigger עובר את הקו הירוק על ה-viewport — ה-ScrollTrigger מתחיל. כשהקו האדום על ה-trigger עובר את הקו האדום — הוא מסתיים.
בואו נראה כמה תרחישים נפוצים ואיך לבחור start/end עבור כל אחד:
toggleActions מגדיר מה קורה ב-4 אירועים: onEnter, onLeave, onEnterBack, onLeaveBack. כל אחד יכול להיות play, pause, resume, reverse, restart, reset, complete, או none. זו שליטה חזקה שמאפשרת לכם להגדיר בדיוק מה קורה בכל תרחיש — כשהמשתמש גולל למטה, כשהוא גולל למעלה, כשהוא חוזר, וכשהוא עוזב:
scrollTrigger: {
trigger: ".section",
start: "top 80%",
// toggleActions: "onEnter onLeave onEnterBack onLeaveBack"
toggleActions: "play none none reverse"
// play כשנכנס, כלום כשיוצא למעלה, כלום כשחוזר מלמעלה, reverse כשיוצא מלמטה
}
// הגדרות toggleActions נפוצות:
// "play none none none" — הכי פשוט. מתנגן פעם אחת, לא חוזר
// "play none none reverse" — מתנגן בכניסה, מתהפך ביציאה. הכי נפוץ
// "play pause resume reverse" — שליטה מלאה על כל מצב
// "restart none none reverse" — מתחיל מחדש בכל כניסה
זה קורה לכולם לפחות פעם: שוכחים להסיר markers: true לפני deploy. פתאום הלקוח רואה קווים ירוקים ואדומים על האתר. תמיד הוסיפו תנאי: markers: window.location.hostname === "localhost" — ככה markers יופיעו רק בפיתוח. או פשוט חפשו "markers" בקוד לפני כל deploy. כלי AI כמו Cursor ו-Bolt לפעמים שוכחים להסיר markers — תמיד בדקו!
scrub הוא ה-property שהופך scroll-triggered ל-scroll-driven. בלי scrub — האנימציה רצה לבד אחרי trigger. עם scrub — האנימציה צמודה לגלילה. אבל scrub מקבל יותר מ-true/false — והערכים השונים יוצרים תחושות שונות לגמרי.
// scrub: true — מיידי, 1:1 עם הגלילה
scrollTrigger: {
scrub: true // כל frame מתעדכן מיד. חד, מדויק, אבל יכול להרגיש "קופצני"
}
// scrub: 0.5 — החלקה של חצי שנייה
scrollTrigger: {
scrub: 0.5 // האנימציה "רודפת" אחרי הגלילה עם עיכוב של 0.5 שניות. חלק ונעים
}
// scrub: 1 — החלקה של שנייה
scrollTrigger: {
scrub: 1 // עיכוב גדול יותר. מרגיש "שמנמן" ומפנק. הכי נפוץ ב-parallax
}
// scrub: 3 — החלקה גדולה
scrollTrigger: {
scrub: 3 // עיכוב משמעותי. מרגיש כמו "אנימציה שצפה". יכול להיות יפה או מעצבן
}
הכלל: scrub: true לדברים שצריכים להיות מדויקים (progress bars, number counters). scrub: 0.5-1 לדברים שצריכים להרגיש חלקים (parallax, movement). scrub: 2+ רק אם אתם יודעים מה אתם עושים — עיכוב גדול מדי מרגיש "שבור".
scrub עובד מצוין עם timeline — כי הוא מאפשר לשלוט על סדרת אנימציות עם הגלילה:
const tl = gsap.timeline({
scrollTrigger: {
trigger: ".story-section",
start: "top top",
end: "+=3000", // 3000px של גלילה
scrub: 1,
pin: true, // נעץ את ה-section (נלמד בסעיף הבא)
markers: true
}
});
tl.to(".title", { opacity: 1, y: 0, duration: 1 })
.to(".subtitle", { opacity: 1, y: 0, duration: 1 }, "-=0.5")
.to(".image", { scale: 1, duration: 2 })
.to(".cta-button", { opacity: 1, y: 0, duration: 0.5 });
// כל ה-timeline ימופה ל-3000px של גלילה
// הראשון 25% של הגלילה = title
// 25-50% = subtitle (עם חפיפה)
// 50-75% = image
// 75-100% = cta-button
שימו לב: כשמשתמשים ב-scrub עם timeline, ה-duration של כל tween הוא יחסי. tween עם duration: 2 יתפוס כפול מ-tween עם duration: 1 — אבל הזמן בפועל נקבע על ידי כמות הגלילה (end - start).
קריטריון הצלחה: גלילה למטה מציגה סדרת אנימציות חלקה. גלילה למעלה מחזירה הכל אחורה. כל שלב תופס חלק פרופורציונלי מהגלילה.
pin הוא אחד הפיצ'רים הכי מרשימים של ScrollTrigger — והוא מה שיוצר את אפקט ה-"storytelling" שרואים באתרים כמו Apple, Stripe, ו-Linear. הרעיון פשוט: האלמנט "נתקע" על המסך (fixed position) בזמן שהמשתמש ממשיך לגלול. התוכן מסביב ממשיך לזוז, אבל האלמנט הנעוץ נשאר במקום — עד שה-ScrollTrigger מסתיים.
למה זה כל כך חזק? כי pin + scrub + timeline = storytelling מלא. אתם יכולים ליצור section שנראה כמו "מצגת" — כל גלילה מגלה שלב נוסף בסיפור, בלי שהמשתמש צריך ללחוץ על כלום.
// pin בסיסי — hero שנתקע למסך
gsap.to(".hero-content", {
opacity: 0,
scale: 0.8,
scrollTrigger: {
trigger: ".hero",
start: "top top",
end: "+=1000", // נעוץ למשך 1000px של גלילה
pin: true, // ← נעץ את ה-.hero
scrub: true,
markers: true
}
});
// מה קורה: ה-hero נשאר fixed, התוכן שלו דועך ומתכווץ,
// והתוכן מתחת "מחכה" עד שה-pin מסתיים
הקומבינציה הכי נפוצה: pin שמחזיק section במקום, ו-timeline שמציג סדרת שלבים בזמן שהמשתמש גולל:
const storyTl = gsap.timeline({
scrollTrigger: {
trigger: ".story",
start: "top top",
end: "+=4000", // 4000px של גלילה "וירטואלית"
pin: true,
scrub: 1,
snap: { // נלמד בהמשך — snap לשלבים
snapTo: 1/4, // 4 שלבים
duration: 0.5
}
}
});
// שלב 1: כותרת
storyTl.from(".step-1", { opacity: 0, y: 100 })
// שלב 2: תמונה ראשונה
.from(".step-2", { opacity: 0, scale: 0.5 })
.to(".step-1", { opacity: 0 }, "<") // step-1 נעלם
// שלב 3: תמונה שנייה
.from(".step-3", { opacity: 0, x: -200 })
.to(".step-2", { opacity: 0 }, "<")
// שלב 4: CTA
.from(".step-4", { opacity: 0, y: 50 })
.to(".step-3", { opacity: 0.3 }, "<");
כש-ScrollTrigger מנעיץ אלמנט, הוא מוסיף "רווח" מתחת — כדי שהתוכן שאחריו לא "יקפוץ" למעלה. זה pinSpacing, והוא מופעל כברירת מחדל. אם אתם רוצים לכבות אותו (נדיר): pinSpacing: false.
// pinSpacing: false — התוכן מתחת לא מקבל רווח
scrollTrigger: {
pin: true,
pinSpacing: false // שימושי כשרוצים ש-sections יחפפו
}
// anticipatePin: 1 — מונע "קפיצה" קטנה בתחילת pin
scrollTrigger: {
pin: true,
anticipatePin: 1 // GSAP מתחיל את ה-pin מעט לפני — תנועה חלקה יותר
}
כש-ScrollTrigger מנעיץ אלמנט, הוא מוציא אותו מה-flow הרגיל (position: fixed). אם ה-trigger element הוא ילד ישיר של flex/grid container — זה יכול לשבור את ה-layout. הפתרון: עטפו את ה-pinned element ב-div נוסף. במקום pin: true על .flex-child, צרו wrapper div ושימו עליו את ה-pin. גם כלי AI נופלים על זה — אם Bolt/Lovable מייצרים pin שנראה שבור, בדקו אם ה-trigger הוא ילד של flex/grid.
Parallax הוא אפקט שבו שכבות שונות נעות במהירויות שונות בזמן גלילה — כמו שעצים קרובים זזים מהר יותר מהרים רחוקים כשנוסעים ברכב. זה אחד האפקטים הכי אהובים בעיצוב web, ועם ScrollTrigger זה פשוט ואלגנטי.
כל שכבה זזה בקצב שונה. שכבת רקע זזה הכי לאט, שכבת תוכן בקצב "רגיל", ואלמנטים צפים זזים הכי מהר. ההפרש במהירויות יוצר תחושת עומק:
// parallax 3 שכבות — background, midground, foreground
// שכבה 1: רקע — זז לאט (50px למעלה על פני כל ה-scroll)
gsap.to(".bg-layer", {
y: -50,
ease: "none",
scrollTrigger: {
trigger: ".parallax-section",
start: "top bottom",
end: "bottom top",
scrub: true
}
});
// שכבה 2: תוכן — זז במהירות רגילה (100px)
gsap.to(".content-layer", {
y: -100,
ease: "none",
scrollTrigger: {
trigger: ".parallax-section",
start: "top bottom",
end: "bottom top",
scrub: true
}
});
// שכבה 3: אלמנטים צפים — זזים מהר (200px)
gsap.to(".float-layer", {
y: -200,
ease: "none",
scrollTrigger: {
trigger: ".parallax-section",
start: "top bottom",
end: "bottom top",
scrub: true
}
});
אפקט parallax קלאסי — תמונת רקע שזזה לאט יותר מהתוכן. הטריק: background-attachment: fixed הוא הדרך הישנה (ועם בעיות ביצועים ב-mobile). הדרך המודרנית — ScrollTrigger עם y:
// HTML: תמונת רקע שגדולה יותר מה-container
// <div class="hero-wrapper" style="overflow: hidden;">
// <img class="hero-bg" src="bg.jpg" style="height: 120%;">
// <div class="hero-content">...</div>
// </div>
gsap.to(".hero-bg", {
y: "-20%", // התמונה זזה למעלה ב-20% של הגובה שלה
ease: "none",
scrollTrigger: {
trigger: ".hero-wrapper",
start: "top top",
end: "bottom top",
scrub: true
}
});
// האפקט: התמונה זזה לאט יותר מהגלילה, מה שיוצר parallax
ScrollSmoother (שהפך לחינם אחרי רכישת Webflow) מאפשר parallax אוטומטי עם data attributes — בלי לכתוב ScrollTrigger ידנית לכל אלמנט:
// שלב 1: רישום פלאגינים
gsap.registerPlugin(ScrollTrigger, ScrollSmoother);
// שלב 2: יצירת smoother
ScrollSmoother.create({
wrapper: "#smooth-wrapper",
content: "#smooth-content",
smooth: 1.5, // כמה smooth (1 = רגיל, 2 = מאוד חלק)
effects: true // מפעיל data-speed ו-data-lag
});
// שלב 3: ב-HTML, פשוט הוסיפו data attributes
// <img data-speed="0.5" src="bg.jpg"> ← זז ב-50% מהמהירות
// <h1 data-speed="1">Title</h1> ← מהירות רגילה
// <div data-speed="1.5">Float</div> ← זז ב-150% מהמהירות
// <div data-lag="0.5">Smooth</div> ← עיכוב חלק
data-speed vs data-lag: data-speed משנה את מהירות התנועה (0.5 = חצי מהירות = parallax רקע). data-lag מוסיף עיכוב חלק — האלמנט "רודף" אחרי הגלילה. שניהם יוצרים תחושת עומק, אבל בדרכים שונות. data-speed הוא parallax קלאסי — שכבות שנעות במהירויות שונות. data-lag הוא אפקט "smooth following" — האלמנט מגיע לאותו מקום, אבל עם עיכוב שיוצר תחושת "ריחוף". אפשר לשלב את שניהם: data-speed="0.8" data-lag="0.3" — שכבה שנעה קצת לאט ובנוסף "רודפת" עם עיכוב.
מתי ScrollSmoother ומתי ScrollTrigger ידני? ScrollSmoother מצוין כשרוצים parallax מהיר על הרבה אלמנטים — פשוט מוסיפים data attributes ב-HTML. ScrollTrigger ידני מצוין כשרוצים שליטה מדויקת — למשל parallax רק ב-section ספציפי, או שינוי מהירות לאורך הגלילה. בפרויקטים גדולים, משלבים את שניהם: ScrollSmoother לאפקט גלובלי, ScrollTrigger לאנימציות ספציפיות. חשוב: ScrollSmoother דורש HTML structure ספציפי — wrapper div שעוטף content div. אם אתם מוסיפים ScrollSmoother לפרויקט קיים, תצטרכו לשנות את מבנה ה-HTML.
קריטריון הצלחה: כשגוללים, כל שכבה זזה במהירות שונה. האפקט צריך להרגיש "טבעי" ולא "מוזר". אם שכבה זזה בכיוון הלא נכון — הפכו את סימן ה-y.
Horizontal scroll הוא אחד האפקטים הכי מרשימים ב-web design — ואחד הכי מבוקשים על ידי מעצבים. הרעיון: המשתמש גולל למטה (כמו תמיד), אבל section מסוים זז לצדדים. זה מושלם לגלריות, תיק עבודות, timelines, ו-product showcases. עם ScrollTrigger + pin, זה פשוט להפליא.
אנחנו מנעיצים (pin) container, ואז מזיזים את התוכן הפנימי שמאלה (או ימינה). המשתמש גולל אנכית, אבל רואה תנועה אופקית. כשהתוכן מגיע לסוף — ה-pin משתחרר והגלילה הרגילה חוזרת.
// HTML Structure:
// <section class="horizontal-section">
// <div class="horizontal-container">
// <div class="panel">Panel 1</div>
// <div class="panel">Panel 2</div>
// <div class="panel">Panel 3</div>
// <div class="panel">Panel 4</div>
// </div>
// </section>
// CSS:
// .horizontal-section { overflow: hidden; }
// .horizontal-container { display: flex; width: 400vw; }
// .panel { width: 100vw; height: 100vh; }
// JavaScript:
const panels = gsap.utils.toArray(".panel");
gsap.to(".horizontal-container", {
x: () => -(document.querySelector(".horizontal-container").scrollWidth - window.innerWidth),
ease: "none",
scrollTrigger: {
trigger: ".horizontal-section",
start: "top top",
end: () => "+=" + document.querySelector(".horizontal-container").scrollWidth,
pin: true,
scrub: 1,
snap: {
snapTo: 1 / (panels.length - 1), // snap לכל panel
duration: 0.3
},
invalidateOnRefresh: true // חישוב מחדש ב-resize
}
});
כמה דגשים חשובים:
Horizontal scroll שנשלט על ידי גלילה אנכית יכול להיות מבלבל במובייל. המשתמש גולל למטה אבל רואה תנועה לצדדים — זה לא אינטואיטיבי לכולם. שלוש אפשרויות: (1) השביתו horizontal scroll במובייל והציגו את ה-panels אנכית רגיל. (2) השתמשו ב-touch swipe אופקי אמיתי (עם Observer plugin). (3) השאירו את ה-horizontal scroll אבל הוסיפו אינדיקציה ויזואלית שמסבירה למשתמש מה קורה. אופציה 1 היא הכי בטוחה.
snap גורם לגלילה "להיצמד" לנקודות מוגדרות. אחרי שהמשתמש מפסיק לגלול, הדף "קופץ" לנקודת ה-snap הקרובה ביותר. זה שימושי ל-fullpage sections, galleries, ו-horizontal scroll.
// snap בסיסי — 4 sections שוות
scrollTrigger: {
snap: {
snapTo: 1/3, // 4 נקודות (0, 1/3, 2/3, 1)
duration: 0.5, // משך "הקפיצה"
ease: "power2.inOut" // easing של הקפיצה
}
}
// snap למערך ערכים ספציפיים
scrollTrigger: {
snap: {
snapTo: [0, 0.25, 0.6, 1], // נקודות לא שוות
duration: { min: 0.2, max: 0.5 } // משך דינמי לפי מרחק
}
}
// snap עם delay — מחכה שהמשתמש יפסיק לגלול
scrollTrigger: {
snap: {
snapTo: 1/4,
duration: 0.4,
delay: 0.1 // מחכה 0.1s אחרי שהגלילה נעצרת
}
}
// snap עם directional — רק בכיוון הגלילה
scrollTrigger: {
snap: {
snapTo: 1/4,
directional: true // snap רק קדימה (אם גוללים למטה)
}
}
מתי להשתמש ב-snap:
ScrollTrigger.batch() מפעיל אנימציה על קבוצת אלמנטים שנכנסים ל-viewport ביחד. במקום ליצור ScrollTrigger נפרד לכל אלמנט (שיכול להיות כבד עם 50+ אלמנטים), batch מאגד אותם:
// batch — אנימציה מדורגת לקבוצת כרטיסים
ScrollTrigger.batch(".card", {
onEnter: (batch) => {
gsap.from(batch, {
opacity: 0,
y: 50,
stagger: 0.1, // stagger בתוך כל batch
duration: 0.6,
ease: "power2.out"
});
},
start: "top 85%", // מתי לשקול "נכנס"
// batchMax: 3 // מקסימום אלמנטים ב-batch אחד
});
// batch עם onLeave — מוסיף reverse
ScrollTrigger.batch(".card", {
onEnter: (batch) => gsap.to(batch, { opacity: 1, y: 0, stagger: 0.1 }),
onLeave: (batch) => gsap.to(batch, { opacity: 0, y: -50, stagger: 0.1 }),
onEnterBack: (batch) => gsap.to(batch, { opacity: 1, y: 0, stagger: 0.1 }),
onLeaveBack: (batch) => gsap.to(batch, { opacity: 0, y: 50, stagger: 0.1 })
});
למה batch ולא ScrollTrigger רגיל?
מ-2023, ל-CSS יש scroll-driven animations — API חדש שמאפשר לחבר CSS animations ל-scroll בלי JavaScript בכלל. זה עובד ב-Chrome/Edge (ומ-2024 גם ב-Firefox). השאלה הגדולה: האם זה מחליף את ScrollTrigger?
התשובה הקצרה: לא. CSS scroll-driven animations הם מצוינים לאפקטים פשוטים (progress bar, parallax בסיסי), אבל ScrollTrigger נשאר חיוני לכל דבר מורכב (pin, snap, batch, horizontal scroll, timelines, callbacks). בואו נראה את ההשוואה:
/* CSS scroll-driven — progress bar */
@keyframes progress {
from { transform: scaleX(0); }
to { transform: scaleX(1); }
}
.progress-bar {
animation: progress linear;
animation-timeline: scroll(); /* צמוד ל-scroll של הדף */
transform-origin: left;
}
/* CSS scroll-driven — element fade-in */
@keyframes fade-in {
from { opacity: 0; transform: translateY(20px); }
to { opacity: 1; transform: translateY(0); }
}
.card {
animation: fade-in linear both;
animation-timeline: view(); /* צמוד ל-visibility ב-viewport */
animation-range: entry 0% entry 100%; /* מתחיל כשנכנס */
}
// ScrollTrigger equivalent — progress bar
gsap.to(".progress-bar", {
scaleX: 1,
ease: "none",
scrollTrigger: {
trigger: "body",
start: "top top",
end: "bottom bottom",
scrub: true
}
});
| קריטריון | CSS scroll-driven | GSAP ScrollTrigger |
|---|---|---|
| ביצועים | מצוין — רץ על compositor thread, לא חוסם main thread | טוב מאוד — אבל רץ על main thread |
| תמיכת דפדפנים | Chrome 115+, Edge 115+, Firefox 110+. Safari עדיין לא (נכון ל-2026) | כל הדפדפנים, כולל Safari, IE11 ואפילו mobile ישן |
| pin | אין support מובנה. position: sticky הוא ה-fallback | pin מובנה, עם pinSpacing ו-anticipatePin |
| snap | CSS scroll-snap (נפרד מ-scroll-driven) | snap מובנה עם שליטה מלאה |
| horizontal scroll | אפשרי אבל מוגבל ומסורבל | פשוט ועוצמתי |
| batch | אין — כל אלמנט צריך הגדרה נפרדת | batch מובנה עם stagger |
| callbacks/events | אין — CSS only, בלי JavaScript hooks | onEnter, onLeave, onUpdate, onToggle ועוד |
| timeline | אין — אנימציה בודדת | timeline מלא עם sequencing ו-labels |
| עקומת למידה | קלה אם מכירים CSS animations | בינונית — צריך ללמוד את ה-API |
| Vibe Coder recommendation | progress bars, simple reveals, parallax בסיסי | כל השאר — pin, storytelling, horizontal, batch, complex sequences |
| השאלה | אם כן | אם לא |
|---|---|---|
| האם צריך pin? | → ScrollTrigger (חובה) | ↓ המשך |
| האם צריך horizontal scroll? | → ScrollTrigger (חובה) | ↓ המשך |
| האם צריך batch/stagger? | → ScrollTrigger (מומלץ) | ↓ המשך |
| האם צריך callbacks (onEnter)? | → ScrollTrigger | ↓ המשך |
| האם חייב לתמוך ב-Safari? | → ScrollTrigger | ↓ המשך |
| האם זה progress bar / parallax פשוט? | → CSS scroll-driven | → ScrollTrigger |
המלצה מעשית ל-Vibe Coders: השתמשו ב-ScrollTrigger כ-default. CSS scroll-driven animations הם "נחמד לדעת" — אבל עדיין לא מכסים מספיק use cases כדי להחליף ScrollTrigger. כשהתמיכה ב-Safari תגיע (צפוי ב-2026-2027) וכשהיכולות יתרחבו — שווה לבדוק שוב. עד אז, ScrollTrigger הוא הבחירה הבטוחה.
כלי AI כמו Bolt, Lovable, Cursor ו-Claude יודעים לייצר ScrollTrigger — אבל האיכות תלויה מאוד באיכות הפרומפט. "add scroll animation" ייתן תוצאה בסיסית. פרומפט שמתאר trigger, start/end, scrub, pin, ו-easing — ייתן תוצאה מקצועית. הנה 5 פרומפטים שעובדים:
Add GSAP ScrollTrigger to this page. Load both gsap and ScrollTrigger via CDN.
Register the plugin with gsap.registerPlugin(ScrollTrigger).
For each section heading (h2), add a scroll-triggered animation:
- gsap.from() with opacity: 0, y: 40, duration: 0.8
- scrollTrigger with trigger set to the heading's parent section
- start: "top 80%"
- toggleActions: "play none none reverse"
- Do NOT use scrub — these should be triggered, not driven
For each card or grid item, use ScrollTrigger.batch():
- onEnter: gsap.from(batch, { opacity: 0, y: 30, stagger: 0.1 })
- start: "top 85%"
Important: Do NOT add markers: true in production code.
Create a storytelling hero section using GSAP ScrollTrigger with pin.
Structure: a .hero section (100vh) with 4 content steps inside it.
Only step 1 is visible initially. Each subsequent step fades in while
the previous one fades out as the user scrolls.
GSAP setup:
- Create a timeline with scrollTrigger on .hero
- start: "top top", end: "+=3000" (3000px of virtual scroll)
- pin: true, scrub: 1
- Add snap: { snapTo: 1/3, duration: 0.4 } for clean step transitions
- Use anticipatePin: 1 to prevent the small jump at pin start
Each step transition: fade out current (opacity 0), fade in next
(opacity 1, y from 30 to 0). Use position parameter "<" to sync.
Make sure pinSpacing is true (default) so content below isn't affected.
Add a parallax effect to the hero section using GSAP ScrollTrigger.
The hero has 3 layers:
1. Background image (.hero-bg) — should move slowly (y: -50, speed: 0.5x)
2. Main content (.hero-content) — normal speed
3. Floating decorative elements (.hero-float) — faster (y: -150, speed: 1.5x)
For each layer, create a separate gsap.to() with:
- scrollTrigger on .hero section
- start: "top top", end: "bottom top"
- scrub: true (not a number — immediate for parallax)
- ease: "none"
The hero should have overflow: hidden to prevent layers from showing
outside boundaries. Background image should be height: 120% to allow
movement room.
Do NOT use ScrollSmoother for this — pure ScrollTrigger only.
Build a horizontal scroll gallery section using GSAP ScrollTrigger.
HTML: a .gallery-section containing a .gallery-track flex container
with 5 .gallery-panel elements. Each panel is 100vw wide, 100vh tall.
JavaScript:
- gsap.to(".gallery-track") with x calculated dynamically:
x: () => -(track.scrollWidth - window.innerWidth)
- scrollTrigger on .gallery-section
- start: "top top"
- end: () => "+=" + track.scrollWidth
- pin: true, scrub: 1
- snap: { snapTo: 1/4, duration: 0.3 }
- invalidateOnRefresh: true (critical for resize handling)
Each panel should have a subtle entrance animation:
- As each panel enters the viewport area, its content fades in
- Use a separate ScrollTrigger for each panel's content with
containerAnimation referencing the horizontal tween
Add a progress indicator at the bottom showing 1/5, 2/5, etc.
Create a complete scroll-driven experience for this landing page
using GSAP ScrollTrigger. The page has these sections:
1. Hero — pin for 1500px. Timeline: logo fades in → title types in
(use SplitText) → subtitle slides up → CTA button appears
2. Features (3 cards) — scroll-triggered, NOT driven. Each card
fades in with stagger using ScrollTrigger.batch(), start: "top 80%"
3. Showcase — horizontal scroll with 4 panels. Pin the section.
Snap to each panel. Progress bar at bottom.
4. Testimonials — parallax background. Cards fade in with batch.
5. CTA — pin briefly (500px), scale up effect on the CTA box.
Global: use scrub: 1 for all scroll-driven sections.
Use toggleActions: "play none none reverse" for all triggered sections.
Load GSAP + ScrollTrigger + SplitText via CDN.
Register all plugins. NO markers in final code.
Performance: use will-change: transform on animated elements.
Use gsap.set() for initial states instead of CSS opacity: 0.
קריטריון הצלחה: ה-AI ייצר קוד עובד עם pin ו-snap. אם לא — בדקו שה-AI טען את ScrollTrigger (registerPlugin) ושה-HTML structure נכון (wrapper + content).
ההבדל בין scroll experience "טוב" ל-"מעולה" הוא לא הקוד — אלא התכנון. לפני שכותבים שורת קוד אחת, צריך לתכנן: איזה sections יש בדף, מה קורה בכל section, מה triggered ומה driven, איפה pin ואיפה לא. בלי תכנון — מקבלים אנימציות אקראיות שלא מספרות סיפור.
השתמשו בתבנית הזו לפני כל פרויקט עם scroll animations:
| Section | גובה (vh / px) | סוג אנימציה | pin? | scrub? | תיאור |
|---|---|---|---|---|---|
| Hero | 100vh + pin 2000px | scroll-driven timeline | כן | scrub: 1 | לוגו → כותרת → תיאור → CTA |
| Features | auto | scroll-triggered batch | לא | לא | 3 כרטיסים נכנסים עם stagger |
| Gallery | 100vh + pin | horizontal scroll | כן | scrub: 1 | 4 panels אופקיים עם snap |
| Stats | 50vh | scroll-driven | לא | scrub: true | counter animations צמודים לגלילה |
| Testimonials | auto | scroll-triggered | לא | לא | fade-in עם toggleActions |
| CTA | 100vh | parallax | לא | scrub: true | רקע parallax + text reveal |
טעות נפוצה: כל section עם אנימציות, אחד אחרי השני, בלי הפסקה. זה כמו לצעוק כל הזמן — אין ניגוד ואין מנוחה. שלבו sections "שקטים" בין sections אנימטיביים. section של טקסט רגיל, section של תמונה גדולה, section עם רווח לבן — כל אלה נותנים "נשימה" שגורמת לאנימציה הבאה להיות משפיעה יותר.
דוגמה מעשית לסדר טוב: Hero (pin + storytelling) → Features (triggered, batch) → תמונה גדולה (בלי אנימציה — נשימה) → Gallery (horizontal scroll) → טקסט עם מספרים (scroll-driven counters) → Testimonials (triggered, batch) → CTA (parallax קל). שימו לב: אחרי כל section "כבד" (pin / horizontal) יש section "קל" (triggered / סטטי). זה יוצר קצב נעים — כמו מוזיקה עם פזמון ובית.
ScrollTrigger בדרך כלל לא מאט את האתר — אבל שימוש לא נכון כן. 3 דברים שיכולים לגרום לבעיות ביצועים: (1) יותר מ-50 ScrollTriggers פעילים באותו זמן — השתמשו ב-batch במקום ScrollTrigger נפרד לכל כרטיס. (2) אנימציות של width/height/top/left — אלה גורמות ל-layout recalculation. השתמשו ב-transform (x, y, scale, rotation) ו-opacity בלבד. (3) אנימציות scroll-driven עם פונקציות onUpdate כבדות — כל frame של גלילה מריץ את הפונקציה. שמרו על onUpdate קל (בלי DOM queries, בלי חישובים כבדים). אם אתם חושדים בבעיית ביצועים — פתחו DevTools → Performance → Record → גללו → עצרו. חפשו "Scripting" גבוה בזמן גלילה.
פיצ'ר מתקדם שכדאי להכיר: containerAnimation מאפשר ליצור ScrollTrigger שמתייחס ל-horizontal scroll (או כל אנימציה צמודה ל-scroll) במקום ל-scroll האנכי הרגיל. במילים פשוטות: אפשר ליצור אנימציות שמופעלות כש-panel ספציפי ב-horizontal scroll נכנס ל-viewport:
// horizontal scroll עם containerAnimation
const horizontalTween = gsap.to(".gallery-track", {
x: () => -(track.scrollWidth - window.innerWidth),
ease: "none",
scrollTrigger: {
trigger: ".gallery-section",
start: "top top",
end: () => "+=" + track.scrollWidth,
pin: true,
scrub: 1,
}
});
// אנימציה בתוך ה-horizontal scroll
gsap.from(".panel-3 .content", {
opacity: 0,
y: 50,
scrollTrigger: {
trigger: ".panel-3",
start: "left center", // שמאל ולא top!
end: "center center",
scrub: true,
containerAnimation: horizontalTween // ← מתייחס ל-horizontal
}
});
שימו לב: כש-containerAnimation מופעל, start ו-end משתמשים ב-left/right/center במקום top/bottom/center — כי הגלילה היא אופקית. זה פיצ'ר מתקדם, אבל הוא מה שהופך horizontal scroll מ-"שקופיות" ל-"חוויה" — כי כל panel יכול לקבל אנימציות משלו.
קריטריון הצלחה: יש לכם תכנון מלא + קוד עובד ב-CodePen או בפרויקט AI. הגלילה מרגישה כמו "סיפור" — לא כמו "אוסף אקראי של אנימציות".
הערה לנגישות — prefers-reduced-motion: חלק מהמשתמשים רגישים לתנועה (motion sickness). צריך לכבד את ההעדפה שלהם:
// בדיקת prefers-reduced-motion
const prefersReducedMotion = window.matchMedia("(prefers-reduced-motion: reduce)").matches;
if (prefersReducedMotion) {
// אופציה 1: בטל את כל ה-ScrollTriggers
ScrollTrigger.getAll().forEach(st => st.kill());
// אופציה 2: בטל רק scrub ו-pin (השאר triggered)
// זה גישה "רכה" יותר — אנימציות פשוטות עדיין קורות
ScrollTrigger.getAll().forEach(st => {
if (st.vars.scrub || st.vars.pin) st.kill();
});
}
// או: הגדירו config גלובלי
if (prefersReducedMotion) {
gsap.config({ force3D: false });
// והשתמשו בתנאי ביצירת כל ScrollTrigger
}
| שלב | פעולה | זמן |
|---|---|---|
| 1. תכנון | מלאו את תבנית תכנון ה-scroll experience (סעיף 5.11). סמנו sections, סוגי אנימציה, pin/scrub | 15 דקות |
| 2. Skeleton | בנו את ה-HTML של כל ה-sections — בלי אנימציות. ודאו ש-layout עובד ותוכן נכון | 20 דקות |
| 3. Markers first | הוסיפו ScrollTrigger לכל section עם markers: true — רק trigger, start, end. בלי אנימציות עדיין. ודאו שה-markers בנקודות הנכונות | 15 דקות |
| 4. Animations | הוסיפו את האנימציות עצמן — tweens, timelines, scrub, pin. אחד-אחד, עם בדיקה אחרי כל אחד | 30 דקות |
| 5. Polish | הוסיפו snap, easing, stagger. כווננו את start/end. התאימו scrub values | 15 דקות |
| 6. Test | בדקו ב-3 גדלי מסך (desktop, tablet, mobile). בדקו prefers-reduced-motion. הסירו markers | 15 דקות |
| 7. Deploy | הסירו markers, בדקו ביצועים (DevTools → Performance), deploy | 10 דקות |
בפרק הזה למדתם ScrollTrigger — הכלי שמחבר אנימציות לגלילה. אבל כלים זה רק חצי מהסיפור. בפרק 6 תלמדו Motion Design — איך לתכנן תנועה שמספרת סיפור. לא "מה הקוד" אלא "למה האנימציה הזו קיימת". תלמדו את 12 העקרונות של אנימציה (מ-Disney!), כללי UX לתנועה, ואיך לתכנן motion שמנחה משתמשים, מעביר מסרים, ויוצר רגש. תשתמשו ב-GSAP וב-ScrollTrigger שלמדתם — אבל הפעם עם מטרה עיצובית ברורה.