→ פרק קודם: GSAP — מנוע האנימציה | פרק הבא: Motion Design ←

פרק 5: ScrollTrigger — קסם שמונע מגלילה

בפרק 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
מתחיל 8 דקות מושג

5.1 מה זה ScrollTrigger ולמה כולם משתמשים בו

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
3 דקות עשו עכשיו: ScrollTrigger ראשון
  1. פתחו CodePen חדש
  2. ב-Settings → JS, הוסיפו את שני ה-CDN links (gsap + ScrollTrigger)
  3. צרו div עם class="box" וגובה 100px, רוחב 100px, רקע כחול, ומרחק 800px מלמעלה (margin-top)
  4. כתבו: gsap.registerPlugin(ScrollTrigger); gsap.from(".box", { opacity: 0, y: 50, duration: 1, scrollTrigger: ".box" });
  5. גללו למטה — ה-box אמור להיכנס עם fade-in + slide-up כשהוא מופיע ב-viewport
מתחיל בינוני 10 דקות מושג קריטי

5.2 scroll-triggered מול scroll-driven — ההבדל שחייבים להבין

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

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 מול scroll-driven
קריטריוןscroll-triggered (בלי scrub)scroll-driven (עם scrub)
תחושהאנימציה "קופצת" — רואים תנועה ברגע ספציפיאנימציה "זורמת" — צמודה לגלילה כמו סרט
שליטת משתמשאין — אחרי ההפעלה, האנימציה רצה לבדמלאה — המשתמש שולט בקצב, כיוון, ועצירה
שימוש עיקריכניסת אלמנטים (fade in, slide up), text reveals, notificationsprogress 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 עם bounce easing

אם תוסיפו scrub: true לאנימציה עם ease: "bounce.out" — תקבלו אפקט מוזר שנראה "שבור". הסיבה: bounce easing מניח שהאנימציה רצה בזמן קבוע, אבל עם scrub הזמן נקבע על ידי הגלילה. כלל אצבע: scrub + bounce/elastic = בעיה. scrub + linear/power1 = מצוין. אם בכל זאת רוצים bounce עם scroll — השתמשו ב-scroll-triggered (בלי scrub) עם toggleActions שמפעיל את האנימציה כש-trigger מגיע.

4 דקות עשו עכשיו: הרגישו את ההבדל
  1. ב-CodePen, צרו שני div-ים זהים (box-a ו-box-b), שניהם ב-margin-top: 600px
  2. ל-box-a: gsap.to(".box-a", { x: 300, duration: 1, scrollTrigger: { trigger: ".box-a", start: "top 80%" } });
  3. ל-box-b: gsap.to(".box-b", { x: 300, scrollTrigger: { trigger: ".box-b", start: "top 80%", end: "top 20%", scrub: true } });
  4. גללו ושימו לב: box-a "קופץ" ברגע ספציפי. box-b "זורם" עם הגלילה
  5. גללו אחורה — box-b חוזר, box-a לא (בהגדרות ברירת מחדל). זה ההבדל!
מתחיל בינוני 12 דקות הגדרות ליבה

5.3 trigger, start, end ו-markers — שליטה מדויקת

שלושת ה-properties הכי חשובים ב-ScrollTrigger הם trigger, start, ו-end. הם קובעים מה מפעיל את האנימציה, מתי היא מתחילה, ומתי היא מסתיימת. בלי להבין אותם — תהיו תקועים עם ניחושים. עם markers:true — תראו בדיוק מה קורה.

trigger — מי מפעיל

ה-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 — הפורמט

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% מלמעלה של ה-trigger80% מלמעלה של המסך
"+=300"300px מתחת ל-top של trigger300px מתחת ל-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 — כלי ה-debug שחייבים

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

בואו נראה כמה תרחישים נפוצים ואיך לבחור start/end עבור כל אחד:

toggleActions — שליטה ב-4 מצבים

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 בפרודקשן = בושה מקצועית

זה קורה לכולם לפחות פעם: שוכחים להסיר markers: true לפני deploy. פתאום הלקוח רואה קווים ירוקים ואדומים על האתר. תמיד הוסיפו תנאי: markers: window.location.hostname === "localhost" — ככה markers יופיעו רק בפיתוח. או פשוט חפשו "markers" בקוד לפני כל deploy. כלי AI כמו Cursor ו-Bolt לפעמים שוכחים להסיר markers — תמיד בדקו!

5 דקות עשו עכשיו: שחקו עם start/end
  1. ב-CodePen, צרו section עם גובה 400px ו-margin-top: 600px
  2. כתבו ScrollTrigger עם markers: true, start: "top 80%", end: "bottom 20%", scrub: true
  3. גללו ושימו לב לקווים הירוקים והאדומים
  4. שנו start ל-"top center" — שימו לב איך הקו הירוק זז
  5. שנו end ל-"+=300" — שימו לב איך זה משנה את אורך ה-ScrollTrigger
  6. נסו "top top" — מתי זה שימושי? (כשרוצים שהאנימציה תתחיל רק כשה-section בראש המסך)
בינוני 10 דקות הגדרה מרכזית

5.4 scrub — הפיכת אנימציה ל-scroll-driven

scrub הוא ה-property שהופך scroll-triggered ל-scroll-driven. בלי scrub — האנימציה רצה לבד אחרי trigger. עם scrub — האנימציה צמודה לגלילה. אבל scrub מקבל יותר מ-true/false — והערכים השונים יוצרים תחושות שונות לגמרי.

scrub: true מול scrub: number

// 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

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).

12 דקות תרגיל: Storytelling section עם scrub
  1. צרו section עם 4 אלמנטים: כותרת, תמונה, טקסט תיאור, וכפתור CTA
  2. בניו timeline עם ScrollTrigger ו-scrub: 1
  3. הגדירו start: "top top", end: "+=2000"
  4. אנימציה: כותרת נכנסת מלמעלה → תמונה מתגלה בזום → טקסט fade in → כפתור slides up
  5. הוסיפו markers: true ובדקו שכל שלב מתחיל בנקודה הנכונה
  6. בונוס: שנו scrub ל-true (מיידי) ואז ל-2 (מושהה) — הרגישו את ההבדל

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

בינוני 12 דקות טכניקה מרכזית

5.5 pin — "הנעץ את זה על המסך"

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 + timeline — storytelling section

הקומבינציה הכי נפוצה: 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 }, "<");

pinSpacing ו-anticipatePin

כש-ScrollTrigger מנעיץ אלמנט, הוא מוסיף "רווח" מתחת — כדי שהתוכן שאחריו לא "יקפוץ" למעלה. זה pinSpacing, והוא מופעל כברירת מחדל. אם אתם רוצים לכבות אותו (נדיר): pinSpacing: false.

// pinSpacing: false — התוכן מתחת לא מקבל רווח
scrollTrigger: {
  pin: true,
  pinSpacing: false  // שימושי כשרוצים ש-sections יחפפו
}

// anticipatePin: 1 — מונע "קפיצה" קטנה בתחילת pin
scrollTrigger: {
  pin: true,
  anticipatePin: 1  // GSAP מתחיל את ה-pin מעט לפני — תנועה חלקה יותר
}
pin + Flexbox/Grid = בעיות layout

כש-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.

5 דקות עשו עכשיו: hero section עם pin
  1. צרו section.hero עם כותרת, תיאור, וכפתור CTA
  2. מתחתיו, צרו section.content עם טקסט ארוך (lorem ipsum)
  3. הוסיפו ScrollTrigger עם pin: true, start: "top top", end: "+=800", scrub: true
  4. האנימציה: opacity של ה-hero content יורד ל-0, scale יורד ל-0.9
  5. גללו — ה-hero אמור להישאר "נעוץ" ולדעוך, עד שה-content section מופיע
  6. הוסיפו markers: true ובדקו שה-pin מתחיל ומסתיים בנקודות הנכונות
בינוני 12 דקות טכניקה

5.6 parallax — עומק דרך מהירויות שונות

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 עם תמונת רקע

אפקט 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 — 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.

15 דקות תרגיל: parallax section עם 3 שכבות
  1. צרו section עם 3 שכבות (z-index): רקע (תמונה/צבע), תוכן (כותרת + טקסט), ואלמנטים צפים (3-4 אייקונים/shapes)
  2. ה-section צריך גובה של 100vh עם overflow: hidden
  3. הוסיפו ScrollTrigger נפרד לכל שכבה עם scrub: true:
  4. רקע: y מ-0 ל--50px. תוכן: y מ-0 ל--100px. אלמנטים צפים: y מ-0 ל--200px
  5. גללו — אתם אמורים לראות אפקט עומק ברור: הרקע זז לאט, התוכן בינוני, האלמנטים הצפים מהר
  6. בונוס: הוסיפו scrub: 1 (במקום true) לשכבת האלמנטים הצפים — שימו לב להחלקה

קריטריון הצלחה: כשגוללים, כל שכבה זזה במהירות שונה. האפקט צריך להרגיש "טבעי" ולא "מוזר". אם שכבה זזה בכיוון הלא נכון — הפכו את סימן ה-y.

3 דקות עשו עכשיו: parallax מהיר עם data-speed
  1. אם יש לכם פרויקט עם ScrollSmoother — הוסיפו data-speed="0.7" לתמונת רקע ו-data-speed="1.2" לכותרת
  2. אם אין — פשוט זכרו את הפתרון הזה. כשתבנו פרויקט עם ScrollSmoother, data-speed הוא הדרך הכי מהירה ל-parallax
  3. הערה: ScrollSmoother דורש HTML structure ספציפי (wrapper + content). אל תנסו להוסיף אותו לפרויקט קיים בלי לקרוא את הדוקומנטציה
בינוני 12 דקות טכניקה מתקדמת

5.7 horizontal scroll — גלילה אופקית עם גלילה אנכית

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 ב-mobile — חשבו פעמיים

Horizontal scroll שנשלט על ידי גלילה אנכית יכול להיות מבלבל במובייל. המשתמש גולל למטה אבל רואה תנועה לצדדים — זה לא אינטואיטיבי לכולם. שלוש אפשרויות: (1) השביתו horizontal scroll במובייל והציגו את ה-panels אנכית רגיל. (2) השתמשו ב-touch swipe אופקי אמיתי (עם Observer plugin). (3) השאירו את ה-horizontal scroll אבל הוסיפו אינדיקציה ויזואלית שמסבירה למשתמש מה קורה. אופציה 1 היא הכי בטוחה.

5 דקות עשו עכשיו: horizontal scroll בסיסי
  1. צרו 4 panels בתוך flex container (כל panel ב-100vw)
  2. העתיקו את הקוד למעלה והתאימו
  3. בדקו שגלילה אנכית מזיזה את ה-panels אופקית
  4. הוסיפו snap ובדקו שהגלילה "נצמדת" לכל panel
  5. נסו לשנות את גודל החלון — ה-invalidateOnRefresh אמור לחשב מחדש
בינוני 10 דקות טכניקות

5.8 snap ו-batch — הצמדה ואצוות

snap — הגלילה "קופצת" למקום

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:

batch — אנימציית קבוצות

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 רגיל?

5 דקות עשו עכשיו: batch animation לרשימת כרטיסים
  1. צרו grid של 12 כרטיסים (4 עמודות, 3 שורות) עם opacity: 0 ו-transform: translateY(50px) כ-CSS ברירת מחדל
  2. הוסיפו ScrollTrigger.batch — כל batch שנכנס ל-viewport מקבל fade in + slide up עם stagger: 0.1
  3. גללו למטה — הכרטיסים אמורים להיכנס בקבוצות, עם עיכוב קל בין כל כרטיס בקבוצה
  4. נסו לשנות את batchMax ולראות איך זה משפיע (batchMax: 2 = מקסימום 2 כרטיסים ב-batch)
בינוני 10 דקות השוואה

5.9 CSS scroll-driven animations מול 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 מול ScrollTrigger
קריטריוןCSS scroll-drivenGSAP ScrollTrigger
ביצועיםמצוין — רץ על compositor thread, לא חוסם main threadטוב מאוד — אבל רץ על main thread
תמיכת דפדפניםChrome 115+, Edge 115+, Firefox 110+. Safari עדיין לא (נכון ל-2026)כל הדפדפנים, כולל Safari, IE11 ואפילו mobile ישן
pinאין support מובנה. position: sticky הוא ה-fallbackpin מובנה, עם pinSpacing ו-anticipatePin
snapCSS scroll-snap (נפרד מ-scroll-driven)snap מובנה עם שליטה מלאה
horizontal scrollאפשרי אבל מוגבל ומסורבלפשוט ועוצמתי
batchאין — כל אלמנט צריך הגדרה נפרדתbatch מובנה עם stagger
callbacks/eventsאין — CSS only, בלי JavaScript hooksonEnter, onLeave, onUpdate, onToggle ועוד
timelineאין — אנימציה בודדתtimeline מלא עם sequencing ו-labels
עקומת למידהקלה אם מכירים CSS animationsבינונית — צריך ללמוד את ה-API
Vibe Coder recommendationprogress 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 הוא הבחירה הבטוחה.

מתחיל בינוני 10 דקות פרומפטים

5.10 פרומפטים AI ל-scroll animations

כלי AI כמו Bolt, Lovable, Cursor ו-Claude יודעים לייצר ScrollTrigger — אבל האיכות תלויה מאוד באיכות הפרומפט. "add scroll animation" ייתן תוצאה בסיסית. פרומפט שמתאר trigger, start/end, scrub, pin, ו-easing — ייתן תוצאה מקצועית. הנה 5 פרומפטים שעובדים:

פרומפט 1: scroll-triggered reveals (מתחיל)

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.

פרומפט 2: hero pin + storytelling (בינוני)

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.

פרומפט 3: parallax hero (בינוני)

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.

פרומפט 4: horizontal scroll gallery (מתקדם)

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.

פרומפט 5: full scroll experience (מתקדם)

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.
15 דקות תרגיל: השתמשו בפרומפט AI ליצירת scroll experience
  1. בחרו כלי AI — Bolt, Lovable, Cursor, או Claude
  2. השתמשו בפרומפט 2 (hero pin + storytelling) כנקודת התחלה
  3. הריצו את הפרומפט וצפו בתוצאה
  4. בדקו: האם ה-AI הוסיף markers? (אם כן — הסירו). האם ה-pin עובד חלק? האם ה-snap קופץ לנקודות הנכונות?
  5. שפרו: שנו את ה-end מ-3000 ל-4000 — האם האנימציה הפכה "אטית" יותר? (כן, כי יש יותר scroll distance לאותו timeline)
  6. בונוס: הוסיפו לפרומפט בקשה ל-SplitText — "Make the title animate letter by letter using SplitText"

קריטריון הצלחה: ה-AI ייצר קוד עובד עם pin ו-snap. אם לא — בדקו שה-AI טען את ScrollTrigger (registerPlugin) ושה-HTML structure נכון (wrapper + content).

בינוני 12 דקות תכנון

5.11 תכנון scroll experience מלא — מתכנון לקוד

ההבדל בין scroll experience "טוב" ל-"מעולה" הוא לא הקוד — אלא התכנון. לפני שכותבים שורת קוד אחת, צריך לתכנן: איזה sections יש בדף, מה קורה בכל section, מה triggered ומה driven, איפה pin ואיפה לא. בלי תכנון — מקבלים אנימציות אקראיות שלא מספרות סיפור.

תבנית תכנון scroll experience

השתמשו בתבנית הזו לפני כל פרויקט עם scroll animations:

Sectionגובה (vh / px)סוג אנימציהpin?scrub?תיאור
Hero100vh + pin 2000pxscroll-driven timelineכןscrub: 1לוגו → כותרת → תיאור → CTA
Featuresautoscroll-triggered batchלאלא3 כרטיסים נכנסים עם stagger
Gallery100vh + pinhorizontal scrollכןscrub: 14 panels אופקיים עם snap
Stats50vhscroll-drivenלאscrub: truecounter animations צמודים לגלילה
Testimonialsautoscroll-triggeredלאלאfade-in עם toggleActions
CTA100vhparallaxלאscrub: trueרקע parallax + text reveal

5 כללים לתכנון scroll experience

  1. אל תנעצו הכל (pin). אם כל section נעוץ — האתר מרגיש "תקוע". Pin שמור ל-1-2 sections מרכזיים — hero ו-showcase. השאר צריך לזרום.
  2. שלבו triggered ו-driven. כניסת אלמנטים = triggered (טבעי, לא מכריח). storytelling/parallax = driven (אינטראקטיבי). אל תשימו scrub על הכל — זה מרגיש "כבד".
  3. ה-first scroll חשוב ביותר. מה קורה כשהמשתמש גולל פעם ראשונה? אם כלום — פספסתם. ה-hero section צריך לגרום למשתמש להרגיש שמשהו קורה מהגלילה הראשונה.
  4. בדקו ב-mobile. אנימציות שנראות מדהים ב-desktop יכולות להיות מעצבנות ב-mobile. Pin ארוך ב-mobile = תסכול. Horizontal scroll ב-mobile = בלבול. תמיד בדקו ושקלו responsive alternatives.
  5. פחות = יותר. 3 אנימציות scroll מעולות טובות מ-15 אנימציות בינוניות. כל אנימציה צריכה "להרוויח את המקום שלה" — אם היא לא מוסיפה ערך (מפנה תשומת לב, מגלה תוכן, מספרת סיפור) — הסירו אותה.

הזמנת sections — "נשימה" בין אנימציות

טעות נפוצה: כל 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 מאט את האתר

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 — אנימציות בתוך horizontal scroll

פיצ'ר מתקדם שכדאי להכיר: 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 יכול לקבל אנימציות משלו.

20 דקות תרגיל: תכננו scroll experience מלא
  1. בחרו נושא: landing page למוצר (אפליקציה, SaaS, מוצר פיזי) — אמיתי או מומצא
  2. תכננו 6-8 sections עם התבנית למעלה. לכל section: שם, גובה, סוג אנימציה, pin/scrub, תיאור
  3. סמנו: מקסימום 2 sections עם pin. מינימום 1 section "שקט" (בלי אנימציה). מינימום 1 batch animation
  4. כתבו פרומפט AI שמתאר את כל ה-scroll experience — השתמשו בפורמט של פרומפט 5 כ-template
  5. הריצו את הפרומפט ובדקו את התוצאה. מה ה-AI עשה נכון? מה צריך לתקן?
  6. בונוס: הוסיפו section אחד עם CSS scroll-driven animation (progress bar) — כדי לתרגל גם את ה-CSS approach

קריטריון הצלחה: יש לכם תכנון מלא + קוד עובד ב-CodePen או בפרויקט AI. הגלילה מרגישה כמו "סיפור" — לא כמו "אוסף אקראי של אנימציות".

3 דקות עשו עכשיו: Debug checklist
  1. שמרו את הרשימה הזו — כשמשהו לא עובד ב-ScrollTrigger, עברו עליה:
  2. הוספתם markers: true? (אם לא — הוסיפו. זה הצעד הראשון תמיד)
  3. registerPlugin(ScrollTrigger) נקרא? (בלי זה — שום דבר לא עובד)
  4. ה-trigger element קיים ב-DOM? (אם הוא רנדר דינמי — ScrollTrigger.refresh())
  5. ה-start מוגדר נכון? (ברירת מחדל: "top bottom" — אולי מוקדם מדי)
  6. scrub vs toggleActions — האם בחרתם את הנכון? (scrub דורס toggleActions)
  7. pin על flex/grid child? (עטפו ב-div)
  8. חסר overflow: hidden על parallax container? (שכבות "בורחות")
  9. invalidateOnRefresh חסר ב-horizontal scroll? (שבירה ב-resize)
  10. end מוגדר? (בלי end, scrub לא יודע מתי לסיים)
  11. אלמנטים עם height: 0 או display: none? (ScrollTrigger לא יכול לחשב מיקום)
צ'קליסט — מה לבדוק לפני שמפרסמים scroll animations

הערה לנגישות — 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
}
שגרת עבודה: scroll animations
שלבפעולהזמן
1. תכנוןמלאו את תבנית תכנון ה-scroll experience (סעיף 5.11). סמנו sections, סוגי אנימציה, pin/scrub15 דקות
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 values15 דקות
6. Testבדקו ב-3 גדלי מסך (desktop, tablet, mobile). בדקו prefers-reduced-motion. הסירו markers15 דקות
7. Deployהסירו markers, בדקו ביצועים (DevTools → Performance), deploy10 דקות
בדקו את עצמכם — מה למדתם בפרק
סיכום הפרק
מה בפרק הבא

בפרק הזה למדתם ScrollTrigger — הכלי שמחבר אנימציות לגלילה. אבל כלים זה רק חצי מהסיפור. בפרק 6 תלמדו Motion Design — איך לתכנן תנועה שמספרת סיפור. לא "מה הקוד" אלא "למה האנימציה הזו קיימת". תלמדו את 12 העקרונות של אנימציה (מ-Disney!), כללי UX לתנועה, ואיך לתכנן motion שמנחה משתמשים, מעביר מסרים, ויוצר רגש. תשתמשו ב-GSAP וב-ScrollTrigger שלמדתם — אבל הפעם עם מטרה עיצובית ברורה.

→ פרק קודם: GSAP — מנוע האנימציה | פרק הבא: Motion Design ←