בפרקים הקודמים למדתם 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 ייחודיות |
כשאתם גוללים בדפדפן, כל פיקסל של גלילה מזיז את הדף מיד — אין עיכוב, אין 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 היא לא בעיה שצריך "לתקן". היא מצוינת ל-90% מהאתרים. smooth scrolling הוא שיפור מכוון ומודע — לאתרים שבהם חוויית הגלילה היא חלק מהעיצוב. אל תוסיפו smooth scrolling לכל פרויקט — תוסיפו אותו כשאתם רוצים ליצור תחושת premium מכוונת. blog פשוט? לא צריך. landing page של מוצר? כנראה כן. portfolio של agency? בהחלט.
לפני 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 מול המתחרים — סיכום מהיר:
| ספרייה | גישה | גודל | ScrollTrigger | סטטוס |
|---|---|---|---|---|
| Lenis | Native scroll + lerp | ~5KB | מושלם | פעיל, הסטנדרט הנוכחי |
| Locomotive Scroll v4 | Virtual scroll (translate3d) | ~15KB | בעייתי (proxy) | ירד בפופולריות |
| Locomotive Scroll v5 | בנוי על Lenis (!) | ~20KB | דרך Lenis | עטיפה סביב Lenis |
| GSAP ScrollSmoother | Native scroll + wrapper | GSAP plugin | מובנה | דורש Club GreenSock (בתשלום) |
| CSS scroll-behavior | CSS native | 0KB | לא קשור | מוגבל ל-anchor links בלבד |
המלצה: אם אתם לא רוצים לשלם ל-GSAP Club — Lenis הוא הבחירה הנכונה. אם יש לכם Club GreenSock — ScrollSmoother הוא גם אופציה מצוינת (אבל locked-in ל-GSAP).
עובדה מעניינת: Locomotive Scroll v5 — שהיה המתחרה הכי רציני — בנוי על Lenis! הצוות של Locomotive החליט להפסיק לפתח את מנוע ה-scroll שלהם ולהשתמש ב-Lenis כבסיס. זו הוכחה ברורה שהקהילה הכריעה: Lenis הוא הסטנדרט.
לפני שכותבים שורת קוד אחת של 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 — עובד ככה. ההבדלים הם בפרטי המימוש, לא בעיקרון.
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();
מתמטית, lerp מתקרב ליעד אבל לעולם לא מגיע (אסימפטוטי). אחרי 60 frames עם factor 0.1, הערך ב-99.8% מהיעד — ההבדל הוא 0.2 פיקסלים, בלתי נראה לעין. Lenis פותר את זה עם threshold — כשהמרחק מהיעד קטן מ-0.1px, הוא פשוט מקפיץ ליעד ועוצר את הלולאה. בלי threshold, Lenis ימשיך לחשב frames מיותרים — בזבוז CPU.
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>
);
}
מה קורה בקוד? בואו נפרק:
new Lenis() — יוצר instance חדש. Lenis מתחיל להקשיב ל-wheel events ול-touch eventslenis.raf(time) — מעדכן את מיקום הגלילה בכל frame. ה-time parameter מגיע מ-requestAnimationFramerequestAnimationFrame(raf) — יוצר לולאת animation שמריצה 60fps. בלי זה, Lenis לא יעדכן את הגלילה<ReactLenis root> עושה את כל זה אוטומטית — יוצר instance, מריץ RAF loop, ומנקה ב-unmountהטעות הנפוצה ביותר ב-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!
html.lenis { height: auto; } .lenis.lenis-smooth { scroll-behavior: auto; })duration ל-2.0 ואז ל-0.5 — הרגישו את ההבדלזו האינטגרציה הכי חשובה בפרק הזה. אתם כבר יודעים 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,
}
});
מה קורה כאן שורה אחרי שורה?
lenis.on('scroll', ScrollTrigger.update) — בכל פעם ש-Lenis מעדכן scroll position, הוא מודיע ל-ScrollTrigger. ככה ScrollTrigger תמיד מסונכרן עם הגלילה החלקהgsap.ticker.add(...) — במקום ליצור RAF loop משלנו, אנחנו משתמשים ב-ticker של GSAP. היתרון: לולאה אחת במקום שתיים = פחות עומס על ה-CPU. ה-time * 1000 ממיר מפורמט GSAP (שניות) לפורמט Lenis (מילישניות)gsap.ticker.lagSmoothing(0) — מכבה את מנגנון ה-lag smoothing של GSAP שיכול להתנגש עם ה-smoothing של Lenis. חשוב!| מצב | גישה | למה |
|---|---|---|
| Lenis בלבד (בלי GSAP) | RAF ידני | אין צורך ב-GSAP ticker — RAF רגיל מספיק |
| Lenis + GSAP ScrollTrigger | GSAP Ticker | לולאה אחת, סנכרון אוטומטי, ביצועים טובים יותר |
| Lenis + React | ReactLenis (אוטומטי) | 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>
gsap.from('.section-title', { opacity: 0, y: 60, stagger: 0.3, scrollTrigger: { trigger: '.section-title', start: 'top 80%' } });טיפים מקצועיים לאינטגרציה:
lenis.destroy() + ScrollTrigger.getAll().forEach(t => t.kill()). בלי cleanup תיהיה דליפת זיכרון ו-zombie scroll triggerslenis.on('scroll', ScrollTrigger.update). הוסיפו ובדקו שובבנו hero section שלם:
בונוס: הוסיפו pin ל-hero section כדי שהוא "ייתקע" בזמן שהאנימציות רצות (hint: pin: true ב-ScrollTrigger).
קריטריון הצלחה: הגלילה חלקה, הכותרת עושה parallax צמוד לגלילה, ה-cards נכנסים ב-timing טבעי. אין קפיצות, אין glitches.
אם אתם בונים עם 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>;
}
ברירת המחדל של Lenis היא smoothTouch: false — כלומר smooth scrolling רק לעכבר/trackpad, לא למגע. אל תשנו את זה. למה? כי smooth scrolling על מובייל הורס את חוויית הגלילה הטבעית של iOS/Android שהמשתמשים רגילים אליה. גלילת מגע כבר חלקה מטבעה — הוספת lerp גורמת לה להרגיש "שבורה" ו"מאוחרת". אם אתם חייבים smooth touch (למשל ב-kiosk display) — הפעילו בזהירות ובדקו על מכשירים אמיתיים.
npm install lenis<ReactLenis root> ב-layout.tsx או _app.tsxעכשיו שיש לכם 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 | תחושה |
|---|---|---|---|
| Portfolio / Photography | 1.5-2.5 | 0.6-0.8 | איטי, luxury, הרבה momentum |
| Agency / Award site | 1.0-1.4 | 0.8-1.0 | חלק ומקצועי, הסטנדרט |
| SaaS / Product | 0.6-1.0 | 1.0-1.2 | מהיר, responsive, לא מעצבן |
| E-commerce / Catalog | 0.5-0.8 | 1.0-1.5 | מהיר מאוד, גלילה אגרסיבית |
| Editorial / Content | 0.8-1.2 | 0.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
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:
if (window.innerWidth < 768) { return; /* skip horizontal setup */ }ScrollTrigger.matchMedia — API מוכן שמאפשר לכם להגדיר ScrollTrigger שונה לכל breakpoint// 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%' }
});
});
}
});
בנו gallery אופקית של פרויקטים:
Hint לـ progress bar: השתמשו ב-onUpdate: (self) => { progressBar.style.width = self.progress * 100 + '%'; } ב-ScrollTrigger.
קריטריון הצלחה: הגלילה חלקה, ה-panels עוברים בצורה טבעית, האלמנטים נכנסים ב-timing נכון, ו-progress bar מסונכרן.
אחד הדברים שמשתמשים מצפים ב-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');
}
});
});
זה הסעיף הכי חשוב בפרק. 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) לתקופות ארוכות
מעבר לחובה המוסרית — Google מעניש אתרים לא נגישים. Lighthouse בודק prefers-reduced-motion, focus management, ו-keyboard navigation. אתר עם smooth scrolling שבור בנגישות יקבל ציון נמוך ב-Accessibility. אם לקוח שלכם (או הבוט של Google) מפעיל prefers-reduced-motion — והאנימציות ממשיכות — זו בעיה. כמה שורות קוד פותרות את זה.
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 מספיקה ועדיפה.
לפני שמסיימים — חייבים לענות על השאלה הכי חשובה: מתי מספיק 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 | Lenis |
|---|---|---|
| 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 size | 0KB | ~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 ימשיך להיות הפתרון.
html { scroll-behavior: smooth; }הנה 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.
בחרו אחד מה-5 פרומפטים (או שלבו כמה):
קריטריון הצלחה: הפרויקט עובד, גלילה חלקה, ScrollTrigger מסונכרן, נגישות מטופלת.
עכשיו שיש לכם את הכלים — בואו נראה איך המקצוענים משתמשים בהם. הנה 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} דק' קריאה נותרו`;
});
שלבו את כל מה שלמדתם בפרק:
Hint: השתמשו בפרומפט 5 (Full Premium Scroll Experience) כנקודת התחלה, ואז שפרו ידנית.
קריטריון הצלחה: אתר שמרגיש premium: גלילה חלקה, אנימציות מתואמות, ניווט עובד, נגיש. משהו שתוכלו להראות בפורטפוליו.
לסיכום ה-patterns — שלוש נקודות חשובות:
document.querySelector('.lenis') — אם חוזר element, האתר משתמש ב-Leniswindow.__lenis או חפשו "lenis" ברשימת ה-scripts ב-Network tabcurrent + (target - current) * factor יוצרת תחושת easingcurrent + (target - current) * factor. ה-factor שולט בחלקות — 0.1 = מאוד חלק, 0.3 = ברירת מחדל, 0.5+ = כמעט מיידיlenis.on('scroll', ScrollTrigger.update) + gsap.ticker.add + gsap.ticker.lagSmoothing(0)בפרק הזה הוספתם את שכבת ה-premium — smooth scrolling שהופך כל אתר לחוויה. בפרק 8 תלמדו SVG Animation — איך להוסיף אנימציות vector שלא מפסידות איכות בשום גודל מסך. תלמדו stroke animation (path שנמשך), morphing (צורה שמשתנה לצורה אחרת), SVG filters, ואיך לשלב SVG animations בתוך חוויית ה-scroll שבניתם עם Lenis + ScrollTrigger. תצאו מהפרק עם אנימציות SVG שמוסיפות שכבה נוספת של מקצועיות לפרויקטים שלכם.