→ פרק קודם: Motion Design | פרק הבא: SVG Animation ←

פרק 7: Lenis + Smooth Scrolling — תחושת הגלילה הפרימיום

בפרקים הקודמים למדתם GSAP, ScrollTrigger ו-Motion Design — עכשיו אתם יודעים לבנות אנימציות scroll מרשימות. אבל יש שכבה אחת שמפרידה בין אתר טוב לבין אתר שמרגיש premium: הגלילה עצמה. לא מה שקורה כשגוללים — אלא איך הגלילה מרגישה. Smooth scrolling הוא מה שגורם לאתרים של Apple, Linear, Awwwards winners ו-agency portfolios להרגיש "חלקלקים" ו"יוקרתיים". Lenis — הספרייה של darkroom.engineering (לשעבר studio-freight) — הפך לסטנדרט התעשייה בתחום. בפרק הזה תלמדו איך Lenis עובד, את המתמטיקה הפשוטה שמאחוריו (lerp), איך לשלב אותו עם GSAP ScrollTrigger, איך לטפל ב-accessibility, ומתי לא להשתמש בו בכלל. תצאו מהפרק עם תחושת גלילה פרימיום שתוכלו להוסיף לכל פרויקט.

מה תקבלו בסוף הפרק
מה תוכלו לעשות אחרי הפרק
לפני שמתחילים
הפרויקט שלך

בפרק 5 בניתם scroll experience מלא עם ScrollTrigger — pin, parallax, horizontal scroll ו-snap. בפרק הזה תוסיפו את שכבת ה-premium: smooth scrolling עם Lenis. תחברו את Lenis ל-GSAP ScrollTrigger שכבר יש לכם, תכוונו את ה-momentum וה-easing עד שהגלילה תרגיש בדיוק נכון, ותוסיפו smooth anchor navigation. בפרק 8 תלמדו SVG Animation ותשלבו אנימציות SVG בתוך חוויית הגלילה החלקה שבניתם.

מילון מונחים
מונח (English)תרגוםהגדרה
Lenisלניסספריית smooth scrolling קוד-פתוח של darkroom.engineering — הסטנדרט הנוכחי לגלילה חלקה. שם מלטינית: "gentle" / "smooth"
lerp (linear interpolation)אינטרפולציה לינאריתהנוסחה המתמטית שמאחורי smooth scrolling: current = current + (target - current) * factor. ה-factor שולט בחלקות — ערך נמוך = יותר חלק ואיטי
smooth scrollingגלילה חלקהטכניקה שמוסיפה easing לגלילת הדף — במקום תנועה חדה פיקסל-בפיקסל, הגלילה "מחליקה" ליעד עם תחושת momentum
scroll momentumמומנטום גלילההתחושה שהגלילה ממשיכה אחרי שמפסיקים לגלול — כמו גלגל שממשיך להסתובב. ב-Lenis: נשלט על ידי duration ו-lerp
inertiaאינרציההעיקרון הפיזי שמאחורי momentum — עצם בתנועה ממשיך בתנועה. ב-smooth scrolling: הדף ממשיך לגלול אחרי שהמשתמש הפסיק
easingעקומת האצהפונקציה שקובעת איך מהירות הגלילה משתנה לאורך זמן. ease-out = מתחיל מהר ומאט בסוף (הכי טבעי לגלילה)
RAF (requestAnimationFrame)בקשת פרייםה-API של הדפדפן שמריץ קוד בכל frame (~60fps). Lenis משתמש ב-RAF כדי לעדכן את מיקום הגלילה 60 פעמים בשנייה
scroll-behaviorהתנהגות גלילהCSS property (scroll-behavior: smooth) שמוסיף החלקה ב-scroll ברמת הדפדפן — פתרון CSS native ללא JavaScript
anchor linkקישור עוגןקישור שגולל לאלמנט ספציפי בדף — href="#section-id". עם Lenis: הגלילה לעוגן הופכת לחלקה אוטומטית
offsetהיסטמרחק שמוסיפים ל-scroll position כשגוללים לעוגן — כדי שהתוכן לא יתחבא מתחת לכותרת fixed. בפיקסלים
studio-freightסטודיו-פרייטהשם הקודם של darkroom.engineering — הסטודיו שיצר את Lenis. חלק מה-packages עדיין תחת @studio-freight
darkroom.engineeringדארקרוםסטודיו פיתוח שיצר את Lenis ועוד ספריות. מתמחה באתרי פרימיום עם חוויות scroll ייחודיות
מתחיל 8 דקות מושג

7.1 מה זה smooth scrolling ולמה זה חשוב

כשאתם גוללים בדפדפן, כל פיקסל של גלילה מזיז את הדף מיד — אין עיכוב, אין easing, אין momentum. זו "native scrolling" — הגלילה הטבעית של הדפדפן. היא מהירה, responsive, ועובדת מצוין בכל מכשיר. אז למה לשנות אותה?

כי יש הבדל בין "עובד" לבין "מרגיש". Native scrolling עובדת. Smooth scrolling מרגישה. ההבדל הוא כמו ההבדל בין נסיעה ברכב ספורט לבין נסיעה ב-BMW — שניהם מגיעים ליעד, אבל אחד מהם גורם לכם להרגיש שהרכב "מגיב" אליכם בצורה אחרת. Smooth scrolling מוסיפה שכבת easing לגלילה — במקום תנועה חדה ומיידית, הדף "מחליק" ליעד עם תחושת momentum ואינרציה.

מה בדיוק משתנה עם smooth scrolling?

למה זה משנה עבור Vibe Coder? כי smooth scrolling הוא אחד הדברים הראשונים שמשתמשים חווים — עוד לפני שהם רואים את האנימציות scroll שלכם. אם הגלילה מרגישה "חלקה" ו"יוקרתית", המשתמש כבר מתחיל לחשוב שהאתר quality. וכשהגלילה משולבת עם ScrollTrigger animations — האפקט מוכפל. האנימציות לא רק קורות כשגוללים — הן קורות בתוך חוויית גלילה שכולה מרגישה premium.

אתרים ב-Awwwards, FWA, ו-CSS Design Awards כמעט תמיד משתמשים ב-smooth scrolling. אם תבדקו את האתרים הזוכים, תראו שרובם משתמשים ב-Lenis או בפתרון דומה. זו לא מקריות — smooth scrolling הוא חלק מה-"DNA" של אתר premium.

ההיסטוריה הקצרה של smooth scrolling באינטרנט (ו-macOS vs Windows): חלק מהנתק בין "native עובד מצוין" לבין "צריך smooth scrolling" הוא הבדלים בין מערכות הפעלה. macOS עם trackpad נותנת חוויית גלילה חלקה מטבעה — momentum, easing, overscroll bounce. Windows עם עכבר רגיל נותנת גלילה "מדרגות" — כל click של הגלגל מזיז מספר קבוע של פיקסלים, בלי שום smoothing. Lenis מאחד את החוויה — כולם מקבלים smooth scrolling, לא משנה מה הם משתמשים. זה במיוחד חשוב ל-designers שעובדים על Mac ומפתחים שבודקים על Windows. הרעיון לא חדש. כבר ב-2015-2016, סטודיואים צרפתיים כמו Locomotive ו-Barba.js התחילו לשחק עם virtual scroll — גלילה שמבוססת על transform: translate3d במקום scroll רגיל. הגישה עבדה, אבל יצרה בעיות: anchor links נשברו, accessibility נפגעה, find-in-page לא עבד, ו-SEO הושפע. ב-2022, Lenis הגיע עם גישה חכמה יותר — native scroll + lerp smoothing — ופתר את כל הבעיות בבת אחת. מאז, הוא הסטנדרט.

מה בדיוק "native scroll" אומר? כשאומרים ש-Lenis משתמש ב-native scroll, הכוונה שערך ה-scrollTop של הדף באמת משתנה — הדפדפן באמת גולל. זה לא translate3d trick שמזייף גלילה. המשמעות: כל ה-APIs הטבעיים של הדפדפן עובדים — IntersectionObserver, scroll events, anchor links, history.scrollRestoration, find-in-page. זה קריטי ל-accessibility, SEO, ולתאימות עם ספריות אחרות.

native scrolling היא לא "רעה"

חשוב להבהיר: native scrolling היא לא בעיה שצריך "לתקן". היא מצוינת ל-90% מהאתרים. smooth scrolling הוא שיפור מכוון ומודע — לאתרים שבהם חוויית הגלילה היא חלק מהעיצוב. אל תוסיפו smooth scrolling לכל פרויקט — תוסיפו אותו כשאתם רוצים ליצור תחושת premium מכוונת. blog פשוט? לא צריך. landing page של מוצר? כנראה כן. portfolio של agency? בהחלט.

3 דקות עשו עכשיו: הרגישו את ההבדל
  1. פתחו אתר רגיל כלשהו (למשל Wikipedia) וגללו — שימו לב שהגלילה ישירה ומיידית
  2. פתחו lenis.darkroom.engineering וגללו — שימו לב ל-momentum (הדף ממשיך אחרי שעוצרים) ול-smoothness
  3. פתחו linear.app — עוד דוגמה מצוינת לגלילה חלקה
  4. שאלו את עצמכם: מה בדיוק מרגיש שונה? מה הייתם רוצים באתר שלכם?
מתחיל בינוני 8 דקות כלי

7.2 Lenis — הסטנדרט של darkroom.engineering

לפני Lenis, עולם ה-smooth scrolling היה מבולגן. היו כמה ספריות — Locomotive Scroll, SmoothScroll, Smooth Scrollbar, ועוד — וכל אחת הייתה שונה, עם בעיות שונות, ואינטגרציה שבורה עם ScrollTrigger. ב-2022, הצוות של studio-freight (שהפך ל-darkroom.engineering) שחרר את Lenis — ותוך שנה הוא הפך לסטנדרט.

למה Lenis ניצח?

שמות וגרסאות — בלבול נפוץ: Lenis עבר כמה שינויים בשמות של ה-npm packages. בהתחלה הוא היה תחת @studio-freight/lenis. ב-2023-2024 הוא עבר ל-lenis (בלי prefix). אם אתם רואים מדריכים ישנים עם @studio-freight/lenis — שימו לב שזה deprecated. השתמשו תמיד ב-lenis (latest). אותו דבר עם React wrapper: @studio-freight/react-lenis הפך ל-lenis/react.

Lenis מול המתחרים — סיכום מהיר:

השוואה: Lenis מול ספריות smooth scrolling אחרות
ספרייהגישהגודלScrollTriggerסטטוס
LenisNative scroll + lerp~5KBמושלםפעיל, הסטנדרט הנוכחי
Locomotive Scroll v4Virtual scroll (translate3d)~15KBבעייתי (proxy)ירד בפופולריות
Locomotive Scroll v5בנוי על Lenis (!)~20KBדרך Lenisעטיפה סביב Lenis
GSAP ScrollSmootherNative scroll + wrapperGSAP pluginמובנהדורש Club GreenSock (בתשלום)
CSS scroll-behaviorCSS native0KBלא קשורמוגבל ל-anchor links בלבד

המלצה: אם אתם לא רוצים לשלם ל-GSAP Club — Lenis הוא הבחירה הנכונה. אם יש לכם Club GreenSock — ScrollSmoother הוא גם אופציה מצוינת (אבל locked-in ל-GSAP).

עובדה מעניינת: Locomotive Scroll v5 — שהיה המתחרה הכי רציני — בנוי על Lenis! הצוות של Locomotive החליט להפסיק לפתח את מנוע ה-scroll שלהם ולהשתמש ב-Lenis כבסיס. זו הוכחה ברורה שהקהילה הכריעה: Lenis הוא הסטנדרט.

2 דקות עשו עכשיו: הכירו את Lenis
  1. כנסו ל-GitHub של Lenis — שימו לב לכמות הכוכבים (מעל 8,000) ולתדירות העדכונים
  2. כנסו ל-דף הבית של Lenis — גללו ושימו לב: הדף עצמו משתמש ב-Lenis (כמובן)
  3. כנסו ל-Docs של Lenis — סקרו את רשימת ה-options וה-methods (אל תקראו הכל, רק סקירה)
מתחיל בינוני 10 דקות מושג מתמטיקה

7.3 lerp — Linear Interpolation: המתמטיקה הפשוטה שמאחורי הקסם

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

lerp (linear interpolation) היא נוסחה שלוקחת ערך נוכחי ומזיזה אותו לכיוון ערך יעד — אבל לא בבת אחת. בכל frame היא מזיזה אותו רק חלק מהדרך:

// הנוסחה של lerp
current = current + (target - current) * factor;

// דוגמה מספרית:
// current = 0 (מיקום נוכחי)
// target = 100 (לאן רוצים להגיע)
// factor = 0.1 (10% מהמרחק בכל frame)

// Frame 1: current = 0 + (100 - 0) * 0.1 = 10
// Frame 2: current = 10 + (100 - 10) * 0.1 = 19
// Frame 3: current = 19 + (100 - 19) * 0.1 = 27.1
// Frame 4: current = 27.1 + (100 - 27.1) * 0.1 = 34.39
// ...
// Frame 20: current ≈ 87.8
// Frame 40: current ≈ 98.5
// Frame 60: current ≈ 99.8 (כמעט שם!)

שימו לב למה שקורה: בתחילת הדרך, כשהמרחק גדול, כל צעד גדול (10 פיקסלים). ככל שמתקרבים ליעד, הצעדים קטנים יותר (1 פיקסל, 0.1 פיקסל...). זה בדיוק מה שיוצר את תחושת ה-ease-out — מתחיל מהר ומאט בסוף. ובלי שום easing function מורכב! רק כפל אחד.

ה-factor שולט בתחושה:

איך Lenis משתמש ב-lerp? ב-60fps (60 frames בשנייה), Lenis מריץ את הנוסחה הזו 60 פעם בשנייה. בכל frame הוא יודע מה ה-target (לאן המשתמש רוצה לגלול — מחושב מה-wheel event), ומה ה-current (מיקום הגלילה כרגע). הנוסחה מזיזה את ה-current לכיוון ה-target, וה-result מוזן ל-window.scrollTo. זה הכל. כל מנוע smooth scrolling — Lenis, Locomotive, ScrollSmoother — עובד ככה. ההבדלים הם בפרטי המימוש, לא בעיקרון.

5 דקות עשו עכשיו: lerp בידיים
  1. פתחו CodePen חדש
  2. צרו div עם class="ball", רוחב וגובה 50px, רקע אדום, border-radius: 50%, position: absolute
  3. הדביקו את הקוד הזה:
const ball = document.querySelector('.ball');
let currentX = 0;
let targetX = 0;
const factor = 0.1; // נסו לשנות: 0.05, 0.1, 0.3, 0.5

document.addEventListener('mousemove', (e) => {
  targetX = e.clientX; // היעד = מיקום העכבר
});

function animate() {
  currentX = currentX + (targetX - currentX) * factor; // lerp!
  ball.style.left = currentX + 'px';
  requestAnimationFrame(animate);
}
animate();
  1. הזיזו את העכבר — הכדור עוקב אחרי העכבר אבל "בעיכוב חלק"
  2. שנו את factor ל-0.05 (מאוד חלק), 0.3 (מהיר), 0.5 (כמעט מיידי) — הרגישו את ההבדל
lerp לעולם לא "מגיע ליעד" באמת

מתמטית, lerp מתקרב ליעד אבל לעולם לא מגיע (אסימפטוטי). אחרי 60 frames עם factor 0.1, הערך ב-99.8% מהיעד — ההבדל הוא 0.2 פיקסלים, בלתי נראה לעין. Lenis פותר את זה עם threshold — כשהמרחק מהיעד קטן מ-0.1px, הוא פשוט מקפיץ ליעד ועוצר את הלולאה. בלי threshold, Lenis ימשיך לחשב frames מיותרים — בזבוז CPU.

מתחיל 10 דקות קוד

7.4 התקנה ושימוש בסיסי

Lenis פשוט להתקנה. שלוש שורות קוד — וחוויית הגלילה באתר שלכם משתנה לחלוטין. יש שלוש דרכים להתקין:

דרך 1: CDN (הכי מהיר להתחלה)

<!-- בתוך <head> — ה-CSS -->
<link rel="stylesheet" href="https://unpkg.com/lenis@latest/dist/lenis.css">

<!-- לפני </body> — ה-JS -->
<script src="https://unpkg.com/lenis@latest/dist/lenis.min.js"></script>
<script>
  const lenis = new Lenis();

  function raf(time) {
    lenis.raf(time);
    requestAnimationFrame(raf);
  }
  requestAnimationFrame(raf);
</script>

דרך 2: npm (לפרויקטים עם bundler — Vite, Webpack, Next.js)

// Terminal
npm install lenis

// בקובץ JS
import Lenis from 'lenis';
import 'lenis/dist/lenis.css';

const lenis = new Lenis();

function raf(time) {
  lenis.raf(time);
  requestAnimationFrame(raf);
}
requestAnimationFrame(raf);

דרך 3: React wrapper (לפרויקטי React/Next.js)

// Terminal
npm install lenis

// בקומפוננט Layout או App
import { ReactLenis } from 'lenis/react';

function App() {
  return (
    <ReactLenis root>
      {/* כל התוכן שלכם כאן */}
      <main>
        <Hero />
        <Features />
        <Footer />
      </main>
    </ReactLenis>
  );
}

מה קורה בקוד? בואו נפרק:

  1. new Lenis() — יוצר instance חדש. Lenis מתחיל להקשיב ל-wheel events ול-touch events
  2. lenis.raf(time) — מעדכן את מיקום הגלילה בכל frame. ה-time parameter מגיע מ-requestAnimationFrame
  3. requestAnimationFrame(raf) — יוצר לולאת animation שמריצה 60fps. בלי זה, Lenis לא יעדכן את הגלילה
  4. ב-React: <ReactLenis root> עושה את כל זה אוטומטית — יוצר instance, מריץ RAF loop, ומנקה ב-unmount
חייבים לולאת RAF!

הטעות הנפוצה ביותר ב-Lenis: שוכחים את לולאת ה-RAF. בלי requestAnimationFrame(raf), Lenis לא עובד — אין smooth scrolling, אין כלום. אם Lenis "לא עובד" — בדקו קודם כל שהלולאה רצה. ב-React עם ReactLenis, הלולאה מנוהלת אוטומטית.

CSS חובה ל-Lenis: Lenis מגיע עם קובץ CSS קטן שחייבים לטעון. הקובץ מגדיר כמה דברים קריטיים:

/* מה שנמצא ב-lenis.css (בפישוט): */
html.lenis,
html.lenis body {
  height: auto;  /* חשוב! מונע בעיות גובה */
}

.lenis.lenis-smooth {
  scroll-behavior: auto;  /* מכבה CSS smooth — Lenis שולט */
}

.lenis.lenis-smooth [data-lenis-prevent] {
  overscroll-behavior: contain;  /* מונע scroll chaining */
}

.lenis.lenis-stopped {
  overflow: hidden;  /* כשגלילה עצורה */
}

data-lenis-prevent — איך לחסום smooth scrolling על אלמנטים ספציפיים: לפעמים יש אלמנטים שלא צריכים smooth scrolling — למשל, dropdown menu עם scroll פנימי, או code block עם overflow-y. הוסיפו data-lenis-prevent כ-attribute ל-HTML element, ו-Lenis ידלג עליו:

<!-- Lenis לא יפריע לגלילה פנימית של האלמנט הזה -->
<div class="scrollable-dropdown" data-lenis-prevent>
  <!-- תוכן עם scroll פנימי -->
</div>

<!-- data-lenis-prevent-wheel — חוסם רק wheel events -->
<div class="modal" data-lenis-prevent-wheel>
  <!-- modal עם scroll פנימי -->
</div>

Options בסיסיים:

const lenis = new Lenis({
  duration: 1.2,        // משך ה-smoothing בשניות (ברירת מחדל: 1.2)
  easing: (t) => Math.min(1, 1.001 - Math.pow(2, -10 * t)),  // easing function
  orientation: 'vertical',  // כיוון: 'vertical' | 'horizontal'
  gestureOrientation: 'vertical',  // כיוון מחוות touch
  smoothWheel: true,    // smooth scrolling לגלגל עכבר
  smoothTouch: false,   // smooth scrolling למגע (ברירת מחדל: false — מומלץ!)
  touchMultiplier: 2,   // מכפיל מהירות ל-touch events
});

// Methods שצריך להכיר:
lenis.scrollTo('#section');     // גלילה חלקה לאלמנט
lenis.scrollTo(500);            // גלילה חלקה ל-500px
lenis.stop();                   // עצירת הגלילה
lenis.start();                  // חידוש הגלילה
lenis.destroy();                // ניקוי — חשוב ב-SPA!
5 דקות עשו עכשיו: Lenis ראשון
  1. פתחו CodePen חדש
  2. ב-HTML: צרו 5 sections עם גובה 100vh כל אחד, צבעי רקע שונים, וטקסט "Section 1-5"
  3. ב-CSS: הוסיפו את ה-link ל-lenis.css (או פשוט html.lenis { height: auto; } .lenis.lenis-smooth { scroll-behavior: auto; })
  4. ב-JS Settings: הוסיפו את ה-CDN של Lenis. ב-JS: הדביקו את קוד ה-CDN מלמעלה
  5. גללו — אתם אמורים להרגיש הבדל מיידי: momentum, smoothness, ease-out בסוף הגלילה
  6. נסו לשנות duration ל-2.0 ואז ל-0.5 — הרגישו את ההבדל
בינוני 12 דקות קוד אינטגרציה

7.5 Lenis + GSAP ScrollTrigger — האינטגרציה הקריטית

זו האינטגרציה הכי חשובה בפרק הזה. אתם כבר יודעים GSAP ScrollTrigger (פרק 5) — ועכשיו אתם מוסיפים Lenis. השאלה היא: איך הם עובדים ביחד? התשובה הקצרה: מושלם — עם 3 שורות חיבור.

למה צריך חיבור? כי Lenis שולט על מתי ואיך הגלילה מתעדכנת. ScrollTrigger צריך לדעת על כל שינוי ב-scroll position כדי להפעיל אנימציות. בלי חיבור — ScrollTrigger עלול "לפספס" עדכוני גלילה, ואנימציות ייראו קופצניות או מתעכבות.

// === Lenis + GSAP ScrollTrigger — Setup מלא ===

import Lenis from 'lenis';
import 'lenis/dist/lenis.css';
import { gsap } from 'gsap';
import { ScrollTrigger } from 'gsap/ScrollTrigger';

gsap.registerPlugin(ScrollTrigger);

// 1. צרו Lenis instance
const lenis = new Lenis();

// 2. חברו את Lenis ל-ScrollTrigger (זה החלק הקריטי!)
lenis.on('scroll', ScrollTrigger.update);

// 3. שלבו את Lenis בתוך ה-ticker של GSAP (במקום RAF ידני)
gsap.ticker.add((time) => {
  lenis.raf(time * 1000); // GSAP ticker מחזיר שניות, Lenis רוצה מילישניות
});

// 4. כבו את ה-lag smoothing של GSAP (למניעת קונפליקט)
gsap.ticker.lagSmoothing(0);

// עכשיו כל ScrollTrigger שתכתבו יעבוד מושלם עם Lenis!
gsap.to('.hero-title', {
  opacity: 1,
  y: 0,
  scrollTrigger: {
    trigger: '.hero-title',
    start: 'top 80%',
    end: 'top 20%',
    scrub: 1,
  }
});

מה קורה כאן שורה אחרי שורה?

  1. lenis.on('scroll', ScrollTrigger.update) — בכל פעם ש-Lenis מעדכן scroll position, הוא מודיע ל-ScrollTrigger. ככה ScrollTrigger תמיד מסונכרן עם הגלילה החלקה
  2. gsap.ticker.add(...) — במקום ליצור RAF loop משלנו, אנחנו משתמשים ב-ticker של GSAP. היתרון: לולאה אחת במקום שתיים = פחות עומס על ה-CPU. ה-time * 1000 ממיר מפורמט GSAP (שניות) לפורמט Lenis (מילישניות)
  3. gsap.ticker.lagSmoothing(0) — מכבה את מנגנון ה-lag smoothing של GSAP שיכול להתנגש עם ה-smoothing של Lenis. חשוב!
RAF ידני vs GSAP Ticker — מתי מה?
מצבגישהלמה
Lenis בלבד (בלי GSAP)RAF ידניאין צורך ב-GSAP ticker — RAF רגיל מספיק
Lenis + GSAP ScrollTriggerGSAP Tickerלולאה אחת, סנכרון אוטומטי, ביצועים טובים יותר
Lenis + ReactReactLenis (אוטומטי)ReactLenis מנהל את הלולאה — לא צריך כלום

CDN version — Setup מלא:

<!-- CSS -->
<link rel="stylesheet" href="https://unpkg.com/lenis@latest/dist/lenis.css">

<!-- JS -->
<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>
<script src="https://unpkg.com/lenis@latest/dist/lenis.min.js"></script>

<script>
  gsap.registerPlugin(ScrollTrigger);

  const lenis = new Lenis();

  lenis.on('scroll', ScrollTrigger.update);

  gsap.ticker.add((time) => {
    lenis.raf(time * 1000);
  });
  gsap.ticker.lagSmoothing(0);

  // עכשיו אפשר לכתוב ScrollTrigger רגיל — הכל עובד!
  gsap.from('.feature-card', {
    opacity: 0,
    y: 80,
    stagger: 0.2,
    scrollTrigger: {
      trigger: '.features-section',
      start: 'top 70%',
    }
  });
</script>
8 דקות עשו עכשיו: Lenis + ScrollTrigger
  1. קחו את ה-CodePen מהתרגיל הקודם (5 sections) או צרו חדש
  2. הוסיפו את ה-CDN של GSAP + ScrollTrigger (בנוסף ל-Lenis)
  3. העתיקו את קוד ה-CDN Setup מלמעלה (3 שורות החיבור)
  4. הוסיפו לכל section כותרת עם class="section-title"
  5. הוסיפו ScrollTrigger: gsap.from('.section-title', { opacity: 0, y: 60, stagger: 0.3, scrollTrigger: { trigger: '.section-title', start: 'top 80%' } });
  6. גללו — הכותרות אמורות להיכנס עם fade-in חלק, בתוך גלילה חלקה. שימו לב: ההבדל מרגיש פרימיום

טיפים מקצועיים לאינטגרציה:

15 דקות תרגיל 1: Hero Section עם Lenis + ScrollTrigger

בנו hero section שלם:

  1. Hero עם כותרת גדולה, תת-כותרת, ו-CTA button
  2. גלילה חלקה עם Lenis (duration: 1.2)
  3. כשגוללים: הכותרת עושה parallax (scrub: true, y: -100) — נעה לאט למעלה
  4. תת-כותרת עושה fade-out (opacity: 0) כשגוללים מעבר ל-hero
  5. Section שני עם 3 feature cards שנכנסים ב-stagger (scroll-triggered, בלי scrub)

בונוס: הוסיפו pin ל-hero section כדי שהוא "ייתקע" בזמן שהאנימציות רצות (hint: pin: true ב-ScrollTrigger).

קריטריון הצלחה: הגלילה חלקה, הכותרת עושה parallax צמוד לגלילה, ה-cards נכנסים ב-timing טבעי. אין קפיצות, אין glitches.

בינוני 10 דקות קוד React

7.6 Lenis + React — useLenis ו-ReactLenis

אם אתם בונים עם React (או Next.js, Remix, Gatsby) — Lenis מגיע עם React wrapper רשמי שמפשט את העבודה משמעותית. במקום לנהל RAF loop ידנית, cleanup ב-useEffect, ו-refs — אתם מקבלים קומפוננט ReactLenis ו-hook useLenis שעושים הכל.

Setup בסיסי — ReactLenis:

// app/layout.tsx (Next.js App Router) או _app.tsx (Pages Router)
import { ReactLenis } from 'lenis/react';
import 'lenis/dist/lenis.css';

export default function RootLayout({ children }) {
  return (
    <html lang="he" dir="rtl">
      <body>
        <ReactLenis root options={{ duration: 1.2 }}>
          {children}
        </ReactLenis>
      </body>
    </html>
  );
}

// root prop = Lenis יחול על כל הדף (document.documentElement)
// בלי root = Lenis יחול רק על ה-container של ReactLenis

useLenis hook — גישה ל-Lenis instance:

import { useLenis } from 'lenis/react';

function ScrollProgressBar() {
  const [progress, setProgress] = useState(0);

  useLenis((lenis) => {
    // נקרא בכל frame של Lenis
    setProgress(lenis.progress); // 0 = top, 1 = bottom
  });

  return (
    <div
      className="scroll-progress"
      style={{ transform: `scaleX(${progress})` }}
    />
  );
}

// useLenis מחזיר את ה-lenis instance (או null אם אין)
function ScrollToButton() {
  const lenis = useLenis();

  return (
    <button onClick={() => lenis?.scrollTo('#contact')}>
      צרו קשר
    </button>
  );
}

Lenis + GSAP ScrollTrigger ב-React:

// hooks/useSmoothScroll.ts
import { useEffect } from 'react';
import { useLenis } from 'lenis/react';
import { gsap } from 'gsap';
import { ScrollTrigger } from 'gsap/ScrollTrigger';

gsap.registerPlugin(ScrollTrigger);

export function useSmoothScroll() {
  const lenis = useLenis();

  useEffect(() => {
    if (!lenis) return;

    // חיבור Lenis ל-ScrollTrigger
    lenis.on('scroll', ScrollTrigger.update);

    gsap.ticker.add((time) => {
      lenis.raf(time * 1000);
    });
    gsap.ticker.lagSmoothing(0);

    return () => {
      // Cleanup — חשוב ב-React!
      lenis.off('scroll', ScrollTrigger.update);
    };
  }, [lenis]);

  return lenis;
}

// שימוש בקומפוננט:
function HeroSection() {
  useSmoothScroll();
  const heroRef = useRef(null);

  useEffect(() => {
    gsap.from(heroRef.current, {
      opacity: 0,
      y: 80,
      scrollTrigger: {
        trigger: heroRef.current,
        start: 'top 80%',
      }
    });
  }, []);

  return <section ref={heroRef}>...</section>;
}
smoothTouch: false — השאירו את ברירת המחדל

ברירת המחדל של Lenis היא smoothTouch: false — כלומר smooth scrolling רק לעכבר/trackpad, לא למגע. אל תשנו את זה. למה? כי smooth scrolling על מובייל הורס את חוויית הגלילה הטבעית של iOS/Android שהמשתמשים רגילים אליה. גלילת מגע כבר חלקה מטבעה — הוספת lerp גורמת לה להרגיש "שבורה" ו"מאוחרת". אם אתם חייבים smooth touch (למשל ב-kiosk display) — הפעילו בזהירות ובדקו על מכשירים אמיתיים.

5 דקות עשו עכשיו: Lenis ב-React (או קראו)
  1. אם יש לכם פרויקט Next.js/React פתוח — הוסיפו npm install lenis
  2. הוסיפו <ReactLenis root> ב-layout.tsx או _app.tsx
  3. גללו — אתם אמורים לראות smooth scrolling מיידית
  4. אם אין לכם פרויקט React פתוח — קראו את הקוד ותבינו את העיקרון: ReactLenis עוטף, useLenis נותן גישה
בינוני מתקדם 10 דקות כיוונון

7.7 Scroll Momentum + Inertia — כיוונון תחושת הגלילה

עכשיו שיש לכם Lenis עובד — הגיע הזמן לכוונן. ברירות המחדל של Lenis מצוינות, אבל כל פרויקט שונה. portfolio של צלם צריך גלילה איטית ומדיטטיבית. landing page של SaaS צריכה גלילה מהירה ו-responsive. הערכים שתבחרו ישפיעו ישירות על "האישיות" של האתר שלכם.

שלושת הפרמטרים ששולטים בתחושה:

const lenis = new Lenis({
  // 1. duration — כמה זמן לוקח לגלילה "להשלים" (בשניות)
  duration: 1.2,  // ברירת מחדל. 0.5 = מהיר. 2.0 = איטי ו-dreamy

  // 2. easing — עקומת ההאצה
  easing: (t) => Math.min(1, 1.001 - Math.pow(2, -10 * t)),
  // ברירת מחדל — ease-out חזק מאוד. מומלץ לא לשנות אלא אם יש סיבה

  // 3. wheelMultiplier — כמה חזק כל "קליק" של גלגל עכבר
  wheelMultiplier: 1,  // ברירת מחדל. 0.5 = חצי מהירות. 2 = כפול
});

פרופילי תחושה — מתכונים מוכנים:

// 🎨 Portfolio / Photography — איטי, מדיטטיבי, luxury
const lenis = new Lenis({
  duration: 2.0,
  wheelMultiplier: 0.8,
  smoothWheel: true,
  smoothTouch: false,
});

// 🚀 SaaS Landing Page — מהיר, responsive, professional
const lenis = new Lenis({
  duration: 0.8,
  wheelMultiplier: 1.2,
  smoothWheel: true,
  smoothTouch: false,
});

// 📰 Editorial / Blog — חלק אבל לא מוגזם
const lenis = new Lenis({
  duration: 1.0,
  wheelMultiplier: 1,
  smoothWheel: true,
  smoothTouch: false,
});

// 🏢 Agency Portfolio — הסטנדרט של Awwwards sites
const lenis = new Lenis({
  duration: 1.2,
  wheelMultiplier: 1,
  smoothWheel: true,
  smoothTouch: false,
});
טבלת כיוונון — duration × wheelMultiplier
סוג אתרdurationwheelMultiplierתחושה
Portfolio / Photography1.5-2.50.6-0.8איטי, luxury, הרבה momentum
Agency / Award site1.0-1.40.8-1.0חלק ומקצועי, הסטנדרט
SaaS / Product0.6-1.01.0-1.2מהיר, responsive, לא מעצבן
E-commerce / Catalog0.5-0.81.0-1.5מהיר מאוד, גלילה אגרסיבית
Editorial / Content0.8-1.20.9-1.0נעים לקריאה, לא מסיח

כלל אצבע: ככל שיש יותר תוכן — duration נמוך יותר. ככל שהאתר יותר ויזואלי — duration גבוה יותר.

טיפ מקצועי — custom easing function:

// ברירת המחדל של Lenis — ease-out חזק
// (t) => Math.min(1, 1.001 - Math.pow(2, -10 * t))

// ease-out עדין יותר (לינארי יותר בהתחלה)
// (t) => 1 - Math.pow(1 - t, 3)  // cubic ease-out

// ease-out מאוד חזק (עוצר מהר)
// (t) => 1 - Math.pow(1 - t, 5)  // quintic ease-out

// טיפ: ברוב המקרים, ברירת המחדל מצוינת. שנו easing רק אם
// ממש צריך — וגם אז, בדקו על עכבר + trackpad + mobile
5 דקות עשו עכשיו: כיוונון momentum
  1. קחו את ה-CodePen עם Lenis שבניתם קודם
  2. שנו duration ל-2.5 — גללו. שימו לב כמה הגלילה "ממשיכה" אחרי שעוצרים
  3. שנו duration ל-0.5 — גללו. שימו לב שהגלילה כמעט מיידית
  4. שנו wheelMultiplier ל-0.5 — גללו. שימו לב שצריך יותר "סיבובי גלגל" כדי לגלול
  5. חזרו לברירת מחדל (duration: 1.2, wheelMultiplier: 1) — ותבינו למה אלה ברירת המחדל
בינוני מתקדם 12 דקות קוד פטרן

7.8 Horizontal Scroll Sections עם Lenis

Horizontal scroll — גלילה אנכית שמתורגמת לתנועה אופקית — הוא אחד האפקטים הכי מבוקשים באתרי premium. בפרק 5 למדתם horizontal scroll עם GSAP ScrollTrigger. עכשיו נוסיף את Lenis כדי שהגלילה עצמה (ולא רק האנימציה) תרגיש חלקה.

הגישה: Lenis מטפל ב-smooth scrolling הכללי (vertical). ScrollTrigger מטפל ב-horizontal translation + pin. Lenis לא צריך לדעת על ה-horizontal section — הוא פשוט מחליק את הגלילה האנכית, ו-ScrollTrigger ממיר את הגלילה האנכית לתנועה אופקית. הם עובדים יחד בצורה טבעית.

// === Horizontal Scroll Section עם Lenis + ScrollTrigger ===

// HTML structure:
// <section class="horizontal-wrapper">
//   <div class="horizontal-track">
//     <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-wrapper { overflow: hidden; }
// .horizontal-track { display: flex; width: fit-content; }
// .panel { width: 100vw; height: 100vh; flex-shrink: 0; }

// JS (אחרי Lenis + ScrollTrigger setup):
const track = document.querySelector('.horizontal-track');
const panels = document.querySelectorAll('.panel');

gsap.to(track, {
  x: () => -(track.scrollWidth - window.innerWidth),
  ease: 'none',
  scrollTrigger: {
    trigger: '.horizontal-wrapper',
    start: 'top top',
    end: () => `+=${track.scrollWidth - window.innerWidth}`,
    pin: true,
    scrub: 1,           // scrub 1 = smooth transition
    invalidateOnRefresh: true,  // חיוני! מחשב מחדש ב-resize
    anticipatePin: 1,    // מונע "קפיצה" כשה-pin מתחיל
  }
});

למה scrub: 1 ולא scrub: true? עם Lenis, scrub: true (מיידי) יכול להרגיש "קופצני" כי Lenis כבר מוסיף smoothing. scrub: 1 מוסיף שנייה של lag לאנימציה — שמתאים מושלם ל-smoothing של Lenis. אם אתם משתמשים ב-duration גבוה ב-Lenis (1.5+), נסו גם scrub: 1.5.

מובייל — horizontal scroll problematic:

// Responsive horizontal scroll עם matchMedia:
ScrollTrigger.matchMedia({
  // Desktop: horizontal scroll
  "(min-width: 768px)": function() {
    gsap.to(track, {
      x: () => -(track.scrollWidth - window.innerWidth),
      ease: 'none',
      scrollTrigger: {
        trigger: '.horizontal-wrapper',
        start: 'top top',
        end: () => `+=${track.scrollWidth - window.innerWidth}`,
        pin: true,
        scrub: 1,
        invalidateOnRefresh: true,
      }
    });
  },

  // Mobile: no horizontal scroll, panels stacked
  "(max-width: 767px)": function() {
    // כל panel מקבל scroll-triggered animation רגילה
    gsap.utils.toArray('.panel').forEach(panel => {
      gsap.from(panel, {
        opacity: 0,
        y: 40,
        scrollTrigger: { trigger: panel, start: 'top 85%' }
      });
    });
  }
});
10 דקות עשו עכשיו: Horizontal Scroll + Lenis
  1. צרו CodePen חדש עם Lenis + GSAP + ScrollTrigger (CDN setup מסעיף 7.5)
  2. ב-HTML: צרו section רגיל (100vh, רקע כחול), אחריו horizontal-wrapper עם 4 panels, ואחריו section רגיל נוסף
  3. ב-CSS: horizontal-track ב-flex, כל panel ב-100vw × 100vh, צבעים שונים
  4. ב-JS: העתיקו את קוד ה-horizontal scroll מלמעלה
  5. גללו — אתם אמורים לראות: גלילה אנכית חלקה → הגעה ל-horizontal section → גלילה אופקית חלקה → חזרה לגלילה אנכית
  6. בדקו: מה קורה כשמשנים את scrub ל-true? ל-2? הרגישו את ההבדל
20 דקות תרגיל 2: Portfolio Horizontal Gallery

בנו gallery אופקית של פרויקטים:

  1. 4 panels — כל אחד עם תמונה (placeholder), שם פרויקט, ותיאור קצר
  2. Lenis smooth scrolling (duration: 1.4 — portfolio feel)
  3. Horizontal scroll עם pin ו-scrub: 1
  4. בכל panel — האלמנטים נכנסים ב-stagger כשה-panel נכנס לראייה (opacity + y)
  5. progress bar בתחתית שמראה כמה מהגלילה האופקית עברתם

Hint לـ progress bar: השתמשו ב-onUpdate: (self) => { progressBar.style.width = self.progress * 100 + '%'; } ב-ScrollTrigger.

קריטריון הצלחה: הגלילה חלקה, ה-panels עוברים בצורה טבעית, האלמנטים נכנסים ב-timing נכון, ו-progress bar מסונכרן.

מתחיל בינוני 8 דקות קוד UX

אחד הדברים שמשתמשים מצפים ב-landing page הוא ניווט חלק — לוחצים על "Features" בתפריט והדף גולל בצורה חלקה ל-section של ה-features. בלי Lenis, אתם צריכים scroll-behavior: smooth ב-CSS או JavaScript ידני. עם Lenis — זה מובנה, ועובד יפה יותר.

scrollTo בסיסי:

// גלילה ל-element
lenis.scrollTo('#features');

// גלילה למיקום מספרי
lenis.scrollTo(500);  // 500px מלמעלה

// גלילה ללמעלה
lenis.scrollTo(0);    // או lenis.scrollTo('top');

// גלילה ללמטה
lenis.scrollTo('bottom');

// אפשרויות נוספות:
lenis.scrollTo('#features', {
  offset: -80,        // offset — מרחק מהיעד (שלילי = למעלה)
  duration: 2,        // משך אנימציית הגלילה (שניות)
  immediate: false,   // true = קפיצה מיידית (בלי animation)
  lock: false,        // true = חוסם גלילת משתמש בזמן האנימציה
  onComplete: () => console.log('הגענו!'),  // callback
});

offset — למה חייבים אותו? אם יש לכם header fixed (navbar שנשאר למעלה), הוא מסתיר את תחילת ה-section. offset שלילי (למשל -80 ל-header בגובה 80px) פותר את זה — הגלילה עוצרת 80px לפני ה-element, כך שה-element מופיע מתחת ל-header. זו טעות נפוצה מאוד — בונים smooth anchor navigation מצוינת, ואז הכותרת של ה-section מסתתרת מתחת ל-navbar. offset פותר את זה.

URL hash sync — שמירה על history: כשמשתמש לוחץ על anchor link ומגיע ל-section, כדאי לעדכן את ה-URL hash כדי שלחיצה על "back" תחזיר אותו למקום הקודם, וגם כדי שיוכל לשתף link ישיר ל-section:

// עדכון URL hash אחרי גלילה לעוגן
lenis.scrollTo(target, {
  offset: -HEADER_HEIGHT,
  duration: 1.5,
  onComplete: () => {
    // עדכון ה-URL בלי reload
    history.pushState(null, '', target);
  }
});

// טעינה ראשונית — אם ה-URL כבר כולל hash, גללו לשם
if (window.location.hash) {
  // חכו ל-Lenis להיות מוכן
  setTimeout(() => {
    lenis.scrollTo(window.location.hash, {
      offset: -HEADER_HEIGHT,
      immediate: false,  // smooth, לא קפיצה
    });
  }, 100);
}
// === Navigation שלמה עם Lenis ===

// HTML:
// <nav class="fixed-nav">
//   <a href="#hero" class="nav-link">Home</a>
//   <a href="#features" class="nav-link">Features</a>
//   <a href="#pricing" class="nav-link">Pricing</a>
//   <a href="#contact" class="nav-link">Contact</a>
// </nav>

const HEADER_HEIGHT = 80; // גובה ה-navbar

document.querySelectorAll('.nav-link').forEach(link => {
  link.addEventListener('click', (e) => {
    e.preventDefault();
    const target = link.getAttribute('href');
    lenis.scrollTo(target, {
      offset: -HEADER_HEIGHT,
      duration: 1.5,
    });
  });
});

// עדכון active state בזמן גלילה:
lenis.on('scroll', () => {
  const sections = document.querySelectorAll('section[id]');
  const scrollPos = lenis.scroll + HEADER_HEIGHT + 10;

  sections.forEach(section => {
    const top = section.offsetTop;
    const height = section.offsetHeight;
    const id = section.getAttribute('id');
    const link = document.querySelector(`.nav-link[href="#${id}"]`);

    if (scrollPos >= top && scrollPos < top + height) {
      link?.classList.add('active');
    } else {
      link?.classList.remove('active');
    }
  });
});
5 דקות עשו עכשיו: Smooth Navigation
  1. קחו את ה-CodePen עם 5 sections מקודם
  2. הוסיפו nav בראש הדף עם 5 anchor links (אחד לכל section)
  3. הוסיפו id לכל section (section-1, section-2...)
  4. הוסיפו את קוד ה-click handler מלמעלה (עם offset של -60)
  5. לחצו על הלינקים — הדף אמור לגלול בצורה חלקה ל-section הנכון, עם המרחק הנכון מהכותרת
מתחיל בינוני 10 דקות נגישות קריטי

7.10 Accessibility — prefers-reduced-motion, מקלדת ו-focus

זה הסעיף הכי חשוב בפרק. Smooth scrolling יכול להיות נגיש לחלוטין — או להיות סיוט של נגישות. ההבדל הוא בכמה שורות קוד שחייבים להוסיף. בלעדיהם — אתם פוגעים במשתמשים עם רגישות לתנועה, משתמשי מקלדת, ומשתמשי screen readers.

1. prefers-reduced-motion — חובה מוחלטת:

// בדיקה האם המשתמש מבקש פחות תנועה
const prefersReduced = window.matchMedia(
  '(prefers-reduced-motion: reduce)'
).matches;

// אפשרות א: כבו Lenis לגמרי
if (prefersReduced) {
  // אל תיצרו Lenis בכלל — גלילה native
  console.log('Smooth scrolling disabled — user prefers reduced motion');
} else {
  const lenis = new Lenis();
  // ... setup רגיל
}

// אפשרות ב: Lenis עם duration מינימלי (עדיין קצת smooth, אבל פחות)
const lenis = new Lenis({
  duration: prefersReduced ? 0 : 1.2,  // 0 = מיידי
  smoothWheel: !prefersReduced,
});

// אפשרות ג: מאזין לשינויים (המשתמש יכול לשנות בזמן אמת!)
const mediaQuery = window.matchMedia('(prefers-reduced-motion: reduce)');
mediaQuery.addEventListener('change', (e) => {
  if (e.matches) {
    lenis.destroy();  // כבה smooth scrolling
  } else {
    // אתחל מחדש
  }
});

למה זה כל כך חשוב? לפי ארגון הבריאות העולמי, כ-35% מהאוכלוסייה חווה רגישות מסוימת לתנועה (vestibular disorders). זה לא "מקרה קצה" — זה שליש מהמשתמשים הפוטנציאליים שלכם. כשמפעילים prefers-reduced-motion במערכת ההפעלה, הם מצפים שאתרים יכבדו את זה. smooth scrolling שלא נכבה עבורם יכול לגרום לבחילה, סחרחורת, או פשוט חוויה לא נעימה.

2. מקלדת — Tab ו-Focus:

// Lenis לא מפריע ל-Tab navigation כברירת מחדל — טוב!
// אבל צריך לוודא ש-focus scrolling עדיין עובד:

// הבעיה: כש-Lenis פעיל, Tab jump עלול להיראות "רוקד"
// כי Lenis מחליק את ה-scroll ו-focus scroll גם מנסה לגלול

// הפתרון: כבו את ה-Lenis smoothing בזמן Tab navigation
document.addEventListener('keydown', (e) => {
  if (e.key === 'Tab') {
    lenis.stop();  // עצירת smooth scroll בזמן Tab
    // Lenis יחזור אוטומטית בגלילת עכבר הבאה
    setTimeout(() => lenis.start(), 1000);
  }
});

3. Screen Readers:

// Lenis משתמש ב-native scroll (scrollTo) — מצוין! Screen readers
// לא מושפעים כי ה-DOM position לא משתנה (בניגוד ל-translate3d)

// עדיין — ודאו:
// ✅ כל section יש לו heading (h2/h3) — לניווט מהיר
// ✅ anchor links יש להם aria-label ברור
// ✅ skip navigation link בראש הדף
// ✅ אל תחסמו גלילה (lenis.stop) לתקופות ארוכות
Accessibility Checklist — Smooth Scrolling
פגיעה בנגישות = פגיעה ב-SEO ובציון Lighthouse

מעבר לחובה המוסרית — Google מעניש אתרים לא נגישים. Lighthouse בודק prefers-reduced-motion, focus management, ו-keyboard navigation. אתר עם smooth scrolling שבור בנגישות יקבל ציון נמוך ב-Accessibility. אם לקוח שלכם (או הבוט של Google) מפעיל prefers-reduced-motion — והאנימציות ממשיכות — זו בעיה. כמה שורות קוד פותרות את זה.

בינוני מתקדם 8 דקות ביצועים

7.11 Performance — לולאת RAF, מובייל, ומתי לא להשתמש

Lenis קל — ~5KB, לולאת RAF אחת, חישוב lerp פשוט. אבל "קל" לא אומר "בחינם". בואו נבין את העלות ומתי היא שווה את זה.

מה Lenis עולה מבחינת ביצועים?

מובייל — שיקולים מיוחדים:

// כיבוי Lenis על מובייל (אם צריך):
const isMobile = /Android|iPhone|iPad/i.test(navigator.userAgent);
// או טוב יותר: const isMobile = window.innerWidth < 768;

const lenis = isMobile ? null : new Lenis();

if (lenis) {
  lenis.on('scroll', ScrollTrigger.update);
  gsap.ticker.add((time) => lenis.raf(time * 1000));
  gsap.ticker.lagSmoothing(0);
}
// ScrollTrigger ימשיך לעבוד גם בלי Lenis!

מתי לא להשתמש ב-Lenis:

כלל הזהב: אם אתם שואלים "האם צריך smooth scrolling?" — כנראה שלא. smooth scrolling הוא בחירה עיצובית מכוונת, לא ברירת מחדל. תוסיפו אותו רק כשאתם בונים חוויה ויזואלית שבה הגלילה היא חלק מהסיפור. landing page, portfolio, showcase, editorial feature — כן. כל השאר — native scrolling מספיקה ועדיפה.

שגרת עבודה: Lenis Performance Check
  1. DevTools Performance tab: פתחו Performance, הקליטו 5 שניות של גלילה. חפשו את ה-RAF callback — אם הוא לוקח יותר מ-2ms, יש בעיה (לא Lenis — אלא handlers שרצים בכל frame)
  2. בדקו FPS: DevTools → Rendering → FPS Meter. מתחת ל-50fps בזמן גלילה = בעיה
  3. בדקו על מובייל: חברו Android device → Chrome Remote Debugging. בדקו FPS אמיתי
  4. השוו עם/בלי Lenis: כבו Lenis (comment out). אם ה-FPS זהה — Lenis לא הבעיה. אם FPS עולה — שקלו לכבות על mobile
מתחיל בינוני 8 דקות החלטה

7.12 CSS scroll-behavior: smooth מול Lenis — טבלת החלטה

לפני שמסיימים — חייבים לענות על השאלה הכי חשובה: מתי מספיק CSS ומתי צריך Lenis? כי CSS יש לו פתרון native ל-smooth scrolling — שורה אחת של CSS:

/* CSS native smooth scrolling */
html {
  scroll-behavior: smooth;
}

/* עכשיו כל anchor link (#section-id) גולל בצורה חלקה */
/* גם window.scrollTo({ top: 500, behavior: 'smooth' }) עובד */

שורה אחת. אפס JavaScript. אפס bundle size. אז למה בכלל צריך Lenis? כי CSS scroll-behavior מוגבל מאוד:

CSS scroll-behavior: smooth מול Lenis — טבלת החלטה
יכולתCSS scroll-behaviorLenis
Anchor links (scroll to #id)כןכן
Momentum / Inertiaלאכן — שליטה מלאה
כיוונון easingלא — easing קבוע של הדפדפןכן — כל easing function
כיוונון durationלא — הדפדפן מחליטכן — בשניות
Smooth wheel scrollingלא — רק scroll-to, לא גלילה רגילהכן — כל גלילה חלקה
ScrollTrigger integrationלא קשורכן — מובנה
API (stop, start, scrollTo with options)לאכן
Event callbacksלאכן — on('scroll', fn)
Bundle size0KB~5KB
תמיכה בדפדפניםכל הדפדפנים המודרנייםכל הדפדפנים המודרניים
Keyboard/Accessibilityמובנהצריך טיפול ידני

הכלל הפשוט:

טעות נפוצה: "אני צריך scroll-behavior: smooth בכל מקרה". הרבה מפתחים מוסיפים html { scroll-behavior: smooth; } לכל פרויקט כברירת מחדל. זה בסדר — אבל שימו לב: אם יש לכם גם Lenis, ה-CSS יתנגש עם ה-JS. Lenis כולל בקובץ ה-CSS שלו: .lenis.lenis-smooth { scroll-behavior: auto; } — שמכבה את ה-CSS smooth. זו לא באג — זה by design. Lenis שולט על ה-smoothing, לא ה-CSS.

ומה לגבי העתיד? CSS scroll-behavior מתפתח — יש הצעות ל-scroll-behavior עם custom easing ו-duration ב-CSS. כשזה יגיע (ויהיה נתמך בכל הדפדפנים) — יתכן ש-Lenis יהפוך לפחות נחוץ למקרים בסיסיים. אבל ל-patterns מתקדמים (velocity text, ScrollTrigger integration, API callbacks) — JS library כמו Lenis ימשיך להיות הפתרון.

3 דקות עשו עכשיו: CSS smooth בפעולה
  1. פתחו CodePen חדש (בלי Lenis!)
  2. CSS: html { scroll-behavior: smooth; }
  3. HTML: nav עם anchor links + sections עם id
  4. לחצו על הלינקים — גלילה חלקה! אבל שימו לב: גלילה רגילה (עכבר) לא חלקה — כי CSS scroll-behavior משפיע רק על scroll-to, לא על גלילה רגילה
מתחיל בינוני 8 דקות AI פרומפטים

7.13 חמישה פרומפטים AI ל-Smooth Scroll

הנה 5 פרומפטים מוכנים להעתקה שמייצרים smooth scrolling מקצועי. כל פרומפט מותאם לרמת Vibe Coder — לא רק "add smooth scroll" אלא הוראות ברורות שמניבות תוצאה premium. הלקח מפרקים קודמים: ככל שהפרומפט מפורט יותר — התוצאה טובה יותר. "add smooth scroll" מניב תוצאה בינונית. פרומפט עם duration, easing, accessibility, ו-ScrollTrigger integration — מניב תוצאה מקצועית.

טיפ חשוב: אחרי שה-AI מייצר את הקוד, בדקו שלושה דברים: (1) האם יש RAF loop או GSAP ticker? (2) האם יש prefers-reduced-motion check? (3) האם smoothTouch היא false? אם אחד מהם חסר — בקשו תיקון בפרומפט follow-up.

פרומפט 1: Lenis בסיסי + ScrollTrigger

Add smooth scrolling using Lenis (latest version from npm).
Setup: duration 1.2, smoothWheel true, smoothTouch false.
Integration with GSAP ScrollTrigger:
- lenis.on('scroll', ScrollTrigger.update)
- Use GSAP ticker (gsap.ticker.add) instead of manual RAF
- gsap.ticker.lagSmoothing(0)
Add prefers-reduced-motion check: if user prefers reduced motion,
disable Lenis entirely (don't create instance).
Import lenis CSS file.

פרומפט 2: Smooth Anchor Navigation

Add smooth anchor navigation with Lenis:
- All nav links with href="#section-id" should smooth-scroll to target
- Add offset of -80px for fixed header (80px height)
- Add active state highlighting: update nav link classes based on
  current scroll position using lenis.on('scroll')
- Duration for anchor scroll: 1.5 seconds
- Handle prefers-reduced-motion: use native scroll-behavior: smooth
  as fallback when Lenis is disabled.
- Add keyboard accessibility: Tab navigation should work without
  smooth scroll interference (stop Lenis during Tab, restart on wheel).

פרומפט 3: Horizontal Scroll Gallery

Build a horizontal scroll gallery section:
- Use Lenis for smooth vertical scrolling (duration: 1.4)
- Use GSAP ScrollTrigger for horizontal scroll:
  - Pin the wrapper section
  - Translate inner track horizontally based on vertical scroll
  - scrub: 1 (not true, for smoother feel with Lenis)
  - invalidateOnRefresh: true
  - anticipatePin: 1
- 4 panels, each 100vw x 100vh
- Each panel content fades in with stagger when panel enters view
- Add progress indicator bar at bottom
- On mobile (< 768px): disable horizontal scroll, stack panels
  vertically. Disable Lenis on mobile.

פרומפט 4: React/Next.js Setup

Add Lenis smooth scrolling to this Next.js App Router project:
- Install 'lenis' package
- Create ReactLenis wrapper in app/layout.tsx with root prop
- Options: duration 1.2, smoothWheel true, smoothTouch false
- Create custom hook useSmoothScroll that:
  - Gets lenis instance from useLenis
  - Connects to GSAP ScrollTrigger (lenis.on scroll + gsap ticker)
  - Returns lenis instance
  - Handles cleanup on unmount
- Add prefers-reduced-motion support:
  - If user prefers reduced, don't render ReactLenis
  - Use native scroll as fallback
- Import lenis CSS in layout.

פרומפט 5: Full Premium Scroll Experience

Build a premium scroll experience for a portfolio site:
Tech: Lenis + GSAP + ScrollTrigger.
Structure:
1. Hero section with large title, parallax background (scrub, y: -200)
2. About section with text reveal (split text, scroll-triggered)
3. Projects horizontal gallery (4 panels, pin + scrub: 1)
4. Testimonials with stagger cards (batch-style entrance)
5. Contact section with smooth anchor from nav

Lenis config: duration 1.4, wheelMultiplier 0.9 (slightly slower).
Accessibility: prefers-reduced-motion check, keyboard support.
Mobile: disable Lenis, stack horizontal panels vertically.
Performance: use GSAP ticker (not manual RAF), lagSmoothing(0).
Add smooth anchor navigation for all nav links with -80px offset.
20 דקות תרגיל 3: השתמשו בפרומפט AI

בחרו אחד מה-5 פרומפטים (או שלבו כמה):

  1. העתיקו את הפרומפט ל-Bolt / Lovable / Cursor / Claude
  2. בדקו את התוצאה — האם Lenis מותקן נכון? RAF loop או GSAP ticker?
  3. בדקו accessibility — האם יש prefers-reduced-motion check?
  4. אם משהו חסר — הוסיפו follow-up prompt: "Add prefers-reduced-motion check — disable Lenis if user prefers reduced motion"
  5. שפרו: בקשו מה-AI להוסיף דבר אחד שלא ביקשתם — ובדקו אם זה עובד

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

בינוני מתקדם 10 דקות פטרנים השראה

7.14 Professional Patterns — Agency Sites, Portfolios, Editorial

עכשיו שיש לכם את הכלים — בואו נראה איך המקצוענים משתמשים בהם. הנה 4 patterns שחוזרים באתרי premium ותוכלו ליישם מיד. כל pattern מבוסס על דוגמאות אמיתיות מאתרים שזכו ב-Awwwards ו-FWA — לא תיאוריה, אלא קוד שעובד.

למה patterns חשובים? כי smooth scrolling לבד הוא רק רקע. מה שיוצר את ה-"wow" הוא השילוב של smooth scrolling עם אפקטים ספציפיים — text שזז לפי velocity, header שמשתנה, sections שנחשפים. אלה ה-patterns ש-clients ומעסיקים מזהים כ-"premium work".

Pattern 1: Scroll-Velocity Text — טקסט שזז לפי מהירות הגלילה

// טקסט שנע לצד ומשנה מהירות לפי מהירות הגלילה
// נפוץ באתרי agency — שורת טקסט שרצה לצדדים

const marqueeText = document.querySelector('.marquee-text');
let scrollVelocity = 0;

lenis.on('scroll', (e) => {
  scrollVelocity = e.velocity; // Lenis נותן velocity בכל scroll event
});

gsap.to(marqueeText, {
  x: '-50%',  // infinite loop effect
  ease: 'none',
  repeat: -1,
  duration: 20,
  modifiers: {
    x: gsap.utils.unitize(x => {
      // מהירות הגלילה משפיעה על מהירות ה-marquee
      return parseFloat(x) + scrollVelocity * 0.5;
    })
  }
});

Pattern 2: Sticky Header שמשתנה — shrink on scroll

// Header שמצטמצם כשגוללים למטה — פטרן קלאסי
// עם Lenis: מרגיש חלק ו-premium

const header = document.querySelector('.site-header');

lenis.on('scroll', (e) => {
  if (e.scroll > 100) {
    header.classList.add('scrolled');
    // CSS: .scrolled { padding: 0.5rem 2rem; background: rgba(0,0,0,0.9); }
  } else {
    header.classList.remove('scrolled');
  }
});

// שילוב עם GSAP לאנימציה חלקה יותר:
ScrollTrigger.create({
  start: 100,
  onEnter: () => gsap.to(header, { padding: '0.5rem 2rem', duration: 0.3 }),
  onLeaveBack: () => gsap.to(header, { padding: '1.5rem 2rem', duration: 0.3 }),
});

Pattern 3: Section Reveal — sections שנחשפים אחד אחרי השני

// כל section "מחליק" מלמטה ומכסה את הקודם
// פטרן נפוץ ב-editorial sites ו-storytelling pages

document.querySelectorAll('.reveal-section').forEach((section, i) => {
  gsap.fromTo(section,
    { yPercent: 100, borderRadius: '2rem 2rem 0 0' },
    {
      yPercent: 0,
      borderRadius: '0 0 0 0',
      ease: 'none',
      scrollTrigger: {
        trigger: section,
        start: 'top bottom',
        end: 'top top',
        scrub: 1,  // scrub 1 עם Lenis = חלק מושלם
      }
    }
  );
});

// CSS:
// .reveal-section {
//   position: sticky;
//   top: 0;
//   will-change: transform;
// }

Pattern 4: Parallax Image Zoom — תמונה שמתקרבת בגלילה

// תמונת hero שמתקרבת (zoom in) כשגוללים — נפוץ ב-portfolio headers
// Lenis smooth + ScrollTrigger scrub = אפקט cinematographic

const heroImage = document.querySelector('.hero-image');

gsap.fromTo(heroImage,
  { scale: 1, filter: 'brightness(1)' },
  {
    scale: 1.3,
    filter: 'brightness(0.6)',  // מתכהה בזמן zoom
    ease: 'none',
    scrollTrigger: {
      trigger: '.hero-section',
      start: 'top top',
      end: 'bottom top',
      scrub: 1,
      pin: true,  // Hero נשאר במקום בזמן ה-zoom
    }
  }
);

// CSS:
// .hero-section { overflow: hidden; }
// .hero-image { width: 100%; height: 100vh; object-fit: cover; }

Pattern 5: Scroll Progress + Reading Time

// Progress bar + estimated reading time — נהדר ל-editorial
const progressBar = document.querySelector('.progress-bar');
const readingTime = document.querySelector('.reading-time');
const totalHeight = document.documentElement.scrollHeight - window.innerHeight;

lenis.on('scroll', (e) => {
  const progress = e.scroll / totalHeight;
  progressBar.style.transform = `scaleX(${progress})`;

  // זמן קריאה משוער (בהנחה שקוראים 200 מילה לדקה)
  const remainingPercent = 1 - progress;
  const totalReadMinutes = 8; // מחושב מראש
  const remaining = Math.ceil(remainingPercent * totalReadMinutes);
  readingTime.textContent = `${remaining} דק' קריאה נותרו`;
});
25 דקות תרגיל 4: בנו Agency Landing Page

שלבו את כל מה שלמדתם בפרק:

  1. Lenis smooth scrolling (duration: 1.2)
  2. Nav עם smooth anchor links (offset -80)
  3. Hero section עם parallax background (scrub)
  4. Section reveal effect (sections שמחליקים אחד על השני)
  5. Horizontal gallery של פרויקטים (3-4 panels)
  6. Footer עם scroll progress bar
  7. prefers-reduced-motion — כל האנימציות מכובות למשתמשים רגישים

Hint: השתמשו בפרומפט 5 (Full Premium Scroll Experience) כנקודת התחלה, ואז שפרו ידנית.

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

לסיכום ה-patterns — שלוש נקודות חשובות:

  1. Less is more: אל תשלבו את כל ה-patterns בדף אחד. בחרו 2-3 שמתאימים לסיפור שלכם. velocity text + section reveal + progress bar = מספיק. אם תוסיפו גם parallax zoom + horizontal scroll + sticky header — זה יהיה overwhelming
  2. תעדיפו consistency: בחרו סט ערכים (duration, scrub, easing) ותשתמשו בהם בכל הדף. אם scrub: 1 באזור אחד ו-scrub: 0.3 באזור אחר — המשתמש ירגיש חוסר עקביות. חריג: horizontal scroll section יכול לקבל scrub שונה מגלילה רגילה
  3. בדקו על מכשירים אמיתיים: simulator של Chrome DevTools לא מדמה את תחושת הגלילה. חברו מכשיר אמיתי (Chrome Remote Debugging) או לפחות בדקו על trackpad + mouse + mobile — שלושה input methods שונים שנותנים תחושות שונות
5 דקות עשו עכשיו: velocity text
  1. קחו CodePen עם Lenis + GSAP
  2. צרו div עם טקסט ארוך שחוזר ("YOUR NAME — CREATIVE DEVELOPER — ") עם overflow: hidden ו-white-space: nowrap
  3. הוסיפו את קוד ה-velocity text מ-Pattern 1
  4. גללו — שימו לב שהטקסט מאיץ כשגוללים מהר ומאט כשגוללים לאט
  5. שחקו עם המכפיל (0.5 → 2.0) — הרגישו את ההבדל
3 דקות עשו עכשיו: בדקו אתרי Awwwards עם Lenis
  1. כנסו ל-Awwwards — Scrolling Sites
  2. פתחו 3 אתרים שזכו ב-SOTD (Site of the Day)
  3. ב-DevTools Console הקלידו: document.querySelector('.lenis') — אם חוזר element, האתר משתמש ב-Lenis
  4. אם לא — בדקו window.__lenis או חפשו "lenis" ברשימת ה-scripts ב-Network tab
  5. שימו לב לאיך כל אתר כיוונן את ה-momentum — יש שזה איטי ו-dreamy, יש שזה מהיר ו-snappy
בדקו את עצמכם
סיכום הפרק
מה בפרק הבא

בפרק הזה הוספתם את שכבת ה-premium — smooth scrolling שהופך כל אתר לחוויה. בפרק 8 תלמדו SVG Animation — איך להוסיף אנימציות vector שלא מפסידות איכות בשום גודל מסך. תלמדו stroke animation (path שנמשך), morphing (צורה שמשתנה לצורה אחרת), SVG filters, ואיך לשלב SVG animations בתוך חוויית ה-scroll שבניתם עם Lenis + ScrollTrigger. תצאו מהפרק עם אנימציות SVG שמוסיפות שכבה נוספת של מקצועיות לפרויקטים שלכם.

→ פרק קודם: Motion Design | פרק הבא: SVG Animation ←