→ פרק קודם: Lottie & Rive — אנימציות מוכנות | פרק הבא: Three.js, Spline ותלת-מימד ←

פרק 10: Canvas 2D — ציור חופשי, חלקיקים ואמנות גנרטיבית

בפרק 9 למדתם Lottie ו-Rive — אנימציות מוכנות מהמדף שמעלות את הרמה בזמן מינימלי. אבל מה קורה כשאתם צריכים משהו שאף אנימציה מוכנה לא יכולה לעשות? רקע של 500 חלקיקים שמתנהגים כמו כוכבים, קונפטי שמתפוצץ ברגע הרכישה, אפקט אורורה שזז ברקע, או ציור גנרטיבי שנוצר מחדש בכל טעינה? כאן נכנס Canvas 2D — ה-API של הדפדפן לציור פיקסלי חופשי. בניגוד ל-SVG שעובד עם אלמנטי DOM, Canvas הוא משטח ציור ריק שבו אתם שולטים בכל פיקסל. אתם מציירים, מוחקים, ומציירים שוב — 60 פעם בשנייה. זה לא רק כלי טכני — זה הכלי שמאחורי אפקטי ה-particles המרהיבים, הרקעים הדינמיים, וה-generative art שאתם רואים באתרים הכי מרשימים ברשת. בפרק הזה תלמדו את הבסיס של Canvas, תבנו מערכות חלקיקים, תעבדו עם קונפטי ואמנות גנרטיבית, ותבינו מתי Canvas הוא הבחירה הנכונה — ומתי SVG או CSS עדיפים.

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

בפרק 9 שילבתם אנימציות Lottie ו-Rive — אנימציות מוכנות שמעלות את הרמה. בפרק הזה תוסיפו שכבה חדשה לגמרי: אפקטים פיקסליים שלא ניתנים להשגה עם שום כלי אחר. תבנו particle background עדין ל-hero section שלכם, תוסיפו confetti burst שמתפוצץ ברגע הנכון (כפתור CTA, סיום טופס), ותיצרו animated gradient רקע שנושם ומשתנה. בפרק 11 תעברו לתלת-מימד עם Three.js ו-Spline — השכבה האחרונה לפני שמחברים הכל.

מילון מונחים
מונח (English)תרגוםהגדרה
Canvasקנבסאלמנט HTML שמספק משטח ציור פיקסלי. בניגוד ל-SVG שיוצר אלמנטי DOM, Canvas מצייר פיקסלים ישירות — מהיר יותר לאלפי אובייקטים, אבל בלי accessibility מובנית
2D Contextהקשר דו-ממדיהאובייקט שמספק את כל פקודות הציור של Canvas — getContext('2d'). דרכו מציירים צורות, טקסט, תמונות, ו-gradients. כל פעולת ציור עוברת דרך ה-context
requestAnimationFrameבקשת פרייםפונקציית JavaScript שמבקשת מהדפדפן לקרוא לפונקציה שלכם לפני הציור הבא — 60 פעם בשנייה (או בקצב הרענון של המסך). הבסיס לכל animation loop ב-Canvas
Particleחלקיקאובייקט קטן עם מיקום (x, y), מהירות (vx, vy), גודל, צבע, ותוחלת חיים. אלפי חלקיקים יחד יוצרים אפקטים כמו שלג, אש, כוכבים, וקונפטי
fillRectמלבן מלאפקודת Canvas שמציירת מלבן מלא בצבע — fillRect(x, y, width, height). אחת מפקודות הציור הבסיסיות ביותר
arcקשת / עיגולפקודת Canvas שמציירת קשת או עיגול — arc(x, y, radius, startAngle, endAngle). עם startAngle=0 ו-endAngle=Math.PI*2 מקבלים עיגול שלם
clearRectניקוי מלבןפקודת Canvas שמוחקת אזור מהציור — clearRect(x, y, width, height). קריטית ל-animation loop: מנקים הכל לפני ציור מחדש
Offscreen Canvasקנבס מוסתרCanvas שלא מוצג על המסך — משמש לציור מקדים (pre-rendering) או חישובים ב-Web Worker. משפר ביצועים כי לא חוסם את ה-main thread
Pixel Ratioיחס פיקסליםהיחס בין פיקסלים פיזיים לפיקסלים לוגיים — devicePixelRatio. מסכי Retina הם 2x או 3x. Canvas שלא מתחשב ב-pixel ratio ייראה מטושטש
p5.jsפי-חמשספרייה לאמנות גנרטיבית ול-creative coding. עוטפת את Canvas API בממשק פשוט — setup(), draw(), random(), noise(). הכלי המועדף ליצירת generative art
Confettiקונפטיחלקיקים צבעוניים שנופלים או מתפוצצים — אפקט חגיגי. canvas-confetti היא הספרייה הפופולרית ביותר (2KB gzipped, zero dependencies)
Generative Artאמנות גנרטיביתאמנות שנוצרת על ידי אלגוריתם — שילוב של randomness, noise, וכללים מתמטיים. כל ריצה יוצרת תוצאה שונה. Canvas ו-p5.js הם הכלים הנפוצים ביותר ליצירתה
Perlin Noiseרעש פרליןפונקציית רעש שמחזירה ערכים "חלקים" ואורגניים — בניגוד ל-Math.random() שנותן ערכים קופצניים. הבסיס לטקסטורות טבעיות, תנועה אורגנית, ו-terrain generation
Animation Loopלולאת אנימציההדפוס הבסיסי של Canvas animation — clear → update → draw, חוזר 60 פעם בשנייה דרך requestAnimationFrame. כל frame מנקה את הקנבס, מעדכן מצבים, ומצייר מחדש
מתחיל 5 דקות מושג

10.1 מה זה Canvas 2D — ולמה הוא שונה מכל מה שלמדתם

עד עכשיו בקורס, כל הגרפיקה שיצרתם הייתה מבוססת DOM. ב-CSS יצרתם אנימציות על div-ים. ב-SVG יצרתם path-ים ו-circle-ים. ב-Lottie שילבתם אנימציות מוכנות שמרנדרות ל-SVG או Canvas מאחורי הקלעים. בכל המקרים האלה, הדפדפן מנהל אלמנטים — הוא יודע שיש עיגול כאן ומלבן שם, ואתם יכולים לגשת אליהם דרך JavaScript, לתת להם event listeners, ולעשות inspect ב-DevTools.

Canvas 2D עובד אחרת לגמרי. הוא לא יוצר אלמנטים — הוא מצייר פיקסלים. תחשבו על זה כמו ההבדל בין לגו (DOM/SVG) לציור על נייר (Canvas). עם לגו, כל חלק נפרד — אפשר להזיז אותו, להחליף אותו, למצוא אותו. עם ציור על נייר, ברגע שציירתם עיגול — הוא חלק מהנייר. אין "אלמנט עיגול" — יש פיקסלים שצבועים בצורת עיגול.

למה זה חשוב? כי Canvas מאפשר דבר אחד שום טכנולוגיה אחרת לא יכולה: לצייר אלפי אובייקטים ב-60fps. כשיש לכם 500 חלקיקים שנעים על המסך, SVG יצטרך לנהל 500 אלמנטי DOM — כל אחד עם מיקום, style, ו-event listeners. Canvas פשוט מצייר 500 עיגולים על "נייר" — הרבה יותר מהיר. זו הסיבה שכל מערכת חלקיקים, כל אפקט particles, כל רקע דינמי שאתם רואים באתרים מרשימים — בנוי על Canvas.

הנה הרגע ש-Canvas נכנס לפעולה: אתם רוצים רקע עם כוכבים שנעים? Canvas. קונפטי שמתפוצץ? Canvas. אפקט אורורה שנושם? Canvas. particles שעוקבים אחרי העכבר? Canvas. כל פעם שצריך הרבה אובייקטים קטנים שזזים — Canvas הוא התשובה.

2 דקות עשו עכשיו: הרגישו את ההבדל
  1. פתחו particles.js demo — זה Canvas. שימו לב: מאות חלקיקים זזים חלק, מתחברים בקווים, מגיבים לעכבר
  2. עכשיו דמיינו שכל חלקיק היה div עם CSS animation — המחשב היה קורס
  3. העבירו את העכבר לתוך האזור — החלקיקים מגיבים. זו אינטראקציה שרצה 60fps כי Canvas לא מנהל DOM

אבל ל-Canvas יש גם חסרונות ברורים. בגלל שהוא לא יוצר אלמנטי DOM: אין accessibility מובנית (קורא מסך לא רואה את הציור), אין event handling על אובייקטים בודדים (צריך לחשב בעצמכם אם העכבר בתוך עיגול), ואי אפשר לעשות inspect ב-DevTools על מה שצייר Canvas. בנוסף, Canvas הוא pixel-based — אם מגדילים אותו, הציור מיטשטש (בניגוד ל-SVG שנשאר חד תמיד). את הבעיה הזו פותרים עם devicePixelRatio — נלמד על זה בהמשך.

הנה הדרך הכי פשוטה לחשוב על זה:

חשוב להבין: Canvas הוא API נמוך. אתם לא אומרים "הנפש את העיגול" — אתם אומרים "מחק הכל, צייר עיגול ב-x+1, חכה 16ms, חזור". זה מרגיש אחרת מ-GSAP או CSS. אבל בדיוק בגלל שהוא נמוך — אתם יכולים לבנות איתו כל דבר. וכ-Vibe Coders, אתם לא צריכים לכתוב הכל מאפס — תלמדו את המושגים, ותגידו ל-AI מה אתם רוצים עם המילים הנכונות.

Canvas הוא immediate mode — לא retained mode

ב-SVG ו-DOM (retained mode), הדפדפן זוכר מה ציירתם — יש "עץ" של אלמנטים. ב-Canvas (immediate mode), ברגע שציירתם — הדפדפן שכח. אתם צריכים לצייר מחדש הכל בכל frame. אם שכחתם לנקות (clearRect) לפני הציור מחדש — האובייקטים ישאירו שובל. זו הטעות הנפוצה ביותר של מתחילים עם Canvas.

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

10.2 הבסיס — getContext, צורות, צבעים ונתיבים

כדי להתחיל לצייר על Canvas, צריך שני דברים: אלמנט <canvas> ב-HTML, ואת ה-2D context שלו ב-JavaScript. ה-context הוא האובייקט שמספק את כל פקודות הציור — תחשבו עליו כמו "המכחול" שלכם.

// HTML: <canvas id="myCanvas" width="800" height="600"></canvas>

const canvas = document.getElementById('myCanvas');
const ctx = canvas.getContext('2d');

// עכשיו ctx הוא המכחול שלכם — כל ציור עובר דרכו

חשוב: את הגודל של Canvas מגדירים דרך width ו-height attributes ב-HTML — לא דרך CSS. CSS משנה את גודל התצוגה אבל לא את רזולוציית הציור. אם תגדירו Canvas ברוחב 400 ב-HTML ורוחב 800 ב-CSS, הציור ייראה מטושטש כי 400 פיקסלים נמתחים ל-800.

בואו נתחיל עם פקודות הציור הבסיסיות:

מלבנים — הצורה הכי פשוטה

// מלבן מלא (filled)
ctx.fillStyle = '#3b82f6';        // צבע המילוי
ctx.fillRect(50, 50, 200, 100);   // x, y, width, height

// מלבן עם קו מתאר (stroked)
ctx.strokeStyle = '#ef4444';      // צבע הקו
ctx.lineWidth = 3;                // עובי הקו
ctx.strokeRect(50, 200, 200, 100);

// ניקוי אזור (שקוף)
ctx.clearRect(100, 70, 50, 50);   // מוחק מלבן מתוך הציור

שלוש הפקודות האלה — fillRect, strokeRect, clearRect — הן פקודות "מיידיות". הן מציירות/מוחקות ברגע שנקראות. כל שאר הצורות ב-Canvas (עיגולים, קווים, נתיבים) דורשות path — נתיב שמגדיר את הצורה לפני הציור.

עיגולים וקשתות — arc

// עיגול מלא
ctx.beginPath();                          // מתחילים נתיב חדש
ctx.arc(300, 200, 50, 0, Math.PI * 2);    // x, y, radius, startAngle, endAngle
ctx.fillStyle = '#10b981';
ctx.fill();                               // ממלאים את הנתיב

// חצי עיגול עם קו מתאר
ctx.beginPath();
ctx.arc(450, 200, 50, 0, Math.PI);        // רק חצי סיבוב
ctx.strokeStyle = '#f59e0b';
ctx.lineWidth = 3;
ctx.stroke();                             // מציירים קו מתאר

ה-arc מקבל את הזוויות ב-רדיאנים, לא מעלות. עיגול שלם = Math.PI * 2 (360 מעלות). חצי עיגול = Math.PI (180 מעלות). רבע עיגול = Math.PI / 2 (90 מעלות).

קווים ונתיבים — paths

// קו פשוט
ctx.beginPath();
ctx.moveTo(50, 400);       // נקודת התחלה (מרימים את העט)
ctx.lineTo(250, 350);      // מציירים קו ליעד
ctx.lineTo(200, 450);      // ממשיכים קו לנקודה הבאה
ctx.closePath();           // סוגרים את הצורה (מחבר לנקודת ההתחלה)
ctx.fillStyle = '#8b5cf6';
ctx.fill();                // ממלאים את המשולש שיצרנו

// נתיב פתוח (בלי closePath)
ctx.beginPath();
ctx.moveTo(300, 400);
ctx.lineTo(400, 350);
ctx.lineTo(500, 400);
ctx.lineTo(600, 350);
ctx.strokeStyle = '#06b6d4';
ctx.lineWidth = 2;
ctx.stroke();              // מציירים רק את הקו, בלי למלא

הדפוס תמיד אותו דבר: beginPath() → פקודות ציור (moveTo, lineTo, arc) → fill() או stroke(). אם שוכחים beginPath(), הנתיב החדש יתחבר לנתיב הקודם — טעות נפוצה שגורמת לקווים מוזרים.

טקסט וגרדיאנטים

// טקסט
ctx.font = 'bold 32px Arial';
ctx.fillStyle = '#ffffff';
ctx.textAlign = 'center';
ctx.fillText('Hello Canvas!', 400, 100);    // טקסט מלא
ctx.strokeText('Outlined', 400, 150);       // טקסט עם קו מתאר בלבד

// גרדיאנט לינארי
const gradient = ctx.createLinearGradient(0, 0, 800, 0);  // מנקודה לנקודה
gradient.addColorStop(0, '#667eea');                        // צבע התחלה
gradient.addColorStop(1, '#764ba2');                        // צבע סיום
ctx.fillStyle = gradient;
ctx.fillRect(0, 0, 800, 600);                              // מלבן עם גרדיאנט

// גרדיאנט רדיאלי (עיגולי)
const radGrad = ctx.createRadialGradient(400, 300, 0, 400, 300, 300);
radGrad.addColorStop(0, '#fbbf24');
radGrad.addColorStop(1, 'transparent');
ctx.fillStyle = radGrad;
ctx.fillRect(0, 0, 800, 600);
5 דקות עשו עכשיו: הציור הראשון שלכם
  1. פתחו CodePen חדש
  2. ב-HTML הוסיפו: <canvas id="c" width="600" height="400" style="background:#111"></canvas>
  3. ב-JS כתבו:
    const ctx = document.getElementById('c').getContext('2d');
    ctx.fillStyle = '#3b82f6';
    ctx.fillRect(50, 50, 100, 80);
    ctx.beginPath();
    ctx.arc(300, 200, 60, 0, Math.PI * 2);
    ctx.fillStyle = '#ef4444';
    ctx.fill();
  4. אתם אמורים לראות מלבן כחול ועיגול אדום על רקע כהה
  5. שנו את המספרים — x, y, radius — והבינו מה כל מספר עושה
Canvas coordinate system — שונה ממה שמצפים

ב-Canvas, נקודה (0,0) היא הפינה השמאלית העליונה. ציר X הולך ימינה, ציר Y הולך למטה (לא למעלה כמו במתמטיקה). אז y=100 זה למטה מ-y=50. זה אותו דבר כמו ב-CSS — אבל שווה לזכור כי אנשים שמגיעים מרקע מתמטי מצפים להפך.

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

10.3 לולאת האנימציה — requestAnimationFrame ודפוס clear → update → draw

עד עכשיו ציירתם תמונה סטטית. כדי להנפיש — צריך לצייר מחדש 60 פעם בשנייה. וכאן Canvas שונה מהותית מ-CSS או GSAP: אין animation property. אתם צריכים לבנות את הלולאה בעצמכם. הדרך לעשות את זה: requestAnimationFrame.

function animate() {
    // 1. CLEAR — מנקים את כל הקנבס
    ctx.clearRect(0, 0, canvas.width, canvas.height);

    // 2. UPDATE — מעדכנים את המצב (מיקום, מהירות, וכו')
    x += speed;
    if (x > canvas.width) x = 0;   // חוזרים להתחלה

    // 3. DRAW — מציירים הכל מחדש
    ctx.beginPath();
    ctx.arc(x, 200, 20, 0, Math.PI * 2);
    ctx.fillStyle = '#3b82f6';
    ctx.fill();

    // 4. REPEAT — מבקשים את ה-frame הבא
    requestAnimationFrame(animate);
}

let x = 0;
const speed = 2;
animate();  // מתחילים!

זה הדפוס הבסיסי ביותר של כל אנימציית Canvas — ושל כל משחק מחשב, אגב. Clear → Update → Draw → Repeat. כל frame (16.6ms ב-60fps) אתם:

  1. מנקים את הקנבס (clearRect על כל השטח)
  2. מעדכנים את המצב — מזיזים אובייקטים, בודקים התנגשויות, מוסיפים/מסירים חלקיקים
  3. מציירים הכל מחדש — כל עיגול, כל קו, כל חלקיק
  4. מבקשים frame נוסף — requestAnimationFrame קוראת לפונקציה שלכם שוב

למה requestAnimationFrame ולא setInterval? שלוש סיבות:

Delta Time — אנימציה שלא תלויה ב-framerate

במחשב מהיר תקבלו 60fps. במובייל ישן אולי 30fps. אם מזיזים עיגול ב-2 פיקסלים כל frame, במחשב מהיר הוא יזוז 120px בשנייה, ובמובייל 60px. הפתרון: delta time.

let lastTime = 0;

function animate(currentTime) {
    const deltaTime = (currentTime - lastTime) / 1000;  // שניות
    lastTime = currentTime;

    ctx.clearRect(0, 0, canvas.width, canvas.height);

    // במקום x += 2 (תלוי framerate):
    x += speed * deltaTime;  // 120 pixels per second, בלי קשר ל-framerate

    ctx.beginPath();
    ctx.arc(x, 200, 20, 0, Math.PI * 2);
    ctx.fillStyle = '#3b82f6';
    ctx.fill();

    requestAnimationFrame(animate);
}

let x = 0;
const speed = 120;  // pixels per second
requestAnimationFrame(animate);
5 דקות עשו עכשיו: הכדור הראשון שזז
  1. ב-CodePen מהתרגיל הקודם, החליפו את ה-JS בקוד הבא:
    const canvas = document.getElementById('c');
    const ctx = canvas.getContext('2d');
    let x = 0;
    function animate() {
    ctx.clearRect(0, 0, 600, 400);
    x += 2; if (x > 600) x = 0;
    ctx.beginPath(); ctx.arc(x, 200, 25, 0, Math.PI*2);
    ctx.fillStyle = '#3b82f6'; ctx.fill();
    requestAnimationFrame(animate);
    }
    animate();
  2. אתם אמורים לראות עיגול כחול שנע ימינה וחוזר להתחלה
  3. נסו להוריד את ctx.clearRect ותראו מה קורה — העיגול משאיר שובל!
  4. בונוס: הוסיפו let y = 0; y += 1.5; כדי שהכדור ינוע גם למטה באלכסון

עכשיו שהבנתם את הדפוס הבסיסי, אפשר לבנות כל אנימציה. הטריק הוא שכל אובייקט שאתם רוצים להנפיש — שומרים את המצב שלו (x, y, speed, color, size) באובייקט JavaScript, ובכל frame מעדכנים ומציירים. עם אובייקט אחד — זה כדור שזז. עם 500 אובייקטים — זו מערכת חלקיקים.

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

10.4 מערכות חלקיקים — יצירה, עדכון, חיבורים ואינטראקציה

מערכת חלקיקים (particle system) היא הדבר הנפוץ ביותר שבונים עם Canvas באתרים. ראיתם את זה עשרות פעמים: רקע עם נקודות שנעות ומתחברות בקווים, כוכבים שרצים, שלג שיורד, ניצוצות שעוקבים אחרי העכבר. כל אלה זה אותו עיקרון — הרבה אובייקטים קטנים (חלקיקים), כל אחד עם כללים פשוטים, שביחד יוצרים אפקט מרשים.

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

class Particle {
    constructor(canvas) {
        this.x = Math.random() * canvas.width;      // מיקום אקראי
        this.y = Math.random() * canvas.height;
        this.vx = (Math.random() - 0.5) * 2;        // מהירות: -1 עד +1
        this.vy = (Math.random() - 0.5) * 2;
        this.radius = Math.random() * 3 + 1;         // גודל: 1-4
        this.opacity = Math.random() * 0.5 + 0.3;    // שקיפות: 0.3-0.8
    }
}

עכשיו יוצרים מערך של חלקיקים ומפעילים את ה-animation loop:

const particles = [];
const PARTICLE_COUNT = 150;

// יצירת חלקיקים
for (let i = 0; i < PARTICLE_COUNT; i++) {
    particles.push(new Particle(canvas));
}

function animate() {
    ctx.clearRect(0, 0, canvas.width, canvas.height);

    particles.forEach(p => {
        // UPDATE — עדכון מיקום
        p.x += p.vx;
        p.y += p.vy;

        // Wrap around — חלקיק שיוצא מצד אחד נכנס מהצד השני
        if (p.x < 0) p.x = canvas.width;
        if (p.x > canvas.width) p.x = 0;
        if (p.y < 0) p.y = canvas.height;
        if (p.y > canvas.height) p.y = 0;

        // DRAW — ציור החלקיק
        ctx.beginPath();
        ctx.arc(p.x, p.y, p.radius, 0, Math.PI * 2);
        ctx.fillStyle = `rgba(255, 255, 255, ${p.opacity})`;
        ctx.fill();
    });

    requestAnimationFrame(animate);
}

animate();

150 שורות? לא. 30 שורות — וכבר יש לכם רקע מלא חלקיקים שנעים. אבל בואו נעלה רמה.

חיבור קווים בין חלקיקים קרובים

זה האפקט הקלאסי — network particles. חלקיקים שקרובים מחוברים בקו שקוף, וככל שהם מתרחקים הקו נעלם:

function connectParticles() {
    const maxDistance = 120;

    for (let i = 0; i < particles.length; i++) {
        for (let j = i + 1; j < particles.length; j++) {
            const dx = particles[i].x - particles[j].x;
            const dy = particles[i].y - particles[j].y;
            const distance = Math.sqrt(dx * dx + dy * dy);

            if (distance < maxDistance) {
                const opacity = 1 - (distance / maxDistance);  // קרוב = ברור, רחוק = שקוף
                ctx.beginPath();
                ctx.moveTo(particles[i].x, particles[i].y);
                ctx.lineTo(particles[j].x, particles[j].y);
                ctx.strokeStyle = `rgba(255, 255, 255, ${opacity * 0.3})`;
                ctx.lineWidth = 1;
                ctx.stroke();
            }
        }
    }
}

קוראים ל-connectParticles() אחרי ציור כל החלקיקים ב-animation loop. שימו לב ללולאה: אנחנו בודקים כל זוג חלקיקים פעם אחת (j = i + 1), לא פעמיים. עם 150 חלקיקים זה 11,175 בדיקות — אבל כל בדיקה היא חישוב פשוט ו-Canvas עושה את זה ב-2ms.

אינטראקציה עם העכבר

הנגיעה הקטנה שהופכת particles מרקע נחמד לחוויה אינטראקטיבית — העכבר:

const mouse = { x: null, y: null, radius: 150 };

canvas.addEventListener('mousemove', (e) => {
    const rect = canvas.getBoundingClientRect();
    mouse.x = e.clientX - rect.left;
    mouse.y = e.clientY - rect.top;
});

canvas.addEventListener('mouseleave', () => {
    mouse.x = null;
    mouse.y = null;
});

// בתוך ה-update של כל חלקיק:
function updateParticle(p) {
    if (mouse.x !== null) {
        const dx = p.x - mouse.x;
        const dy = p.y - mouse.y;
        const distance = Math.sqrt(dx * dx + dy * dy);

        if (distance < mouse.radius) {
            // דוחפים את החלקיק הרחק מהעכבר
            const force = (mouse.radius - distance) / mouse.radius;
            const angle = Math.atan2(dy, dx);
            p.x += Math.cos(angle) * force * 3;
            p.y += Math.sin(angle) * force * 3;
        }
    }

    p.x += p.vx;
    p.y += p.vy;
}

עכשיו כשהעכבר מתקרב לחלקיק — החלקיק "בורח". זה יוצר אפקט magnetic repulsion שנראה טבעי ומרשים. רוצים אפקט הפוך — שהחלקיקים נמשכים לעכבר? פשוט תהפכו את הכיוון: p.x -= Math.cos(angle) * force * 3.

15 דקות תרגיל: Particle Network מלא

בנו particle network background שלם. הדרך הקלה ביותר: בקשו מ-AI. הנה הפרומפט:

"Build a Canvas 2D particle network background. 120 white particles on dark (#0f172a) background, connected by lines when closer than 120px. Line opacity fades with distance. Particles bounce off edges. Mouse interaction: particles push away within 150px radius. Canvas should be fullscreen (window.innerWidth/Height), resize on window resize, use devicePixelRatio for sharp rendering. Include requestAnimationFrame loop with clear-update-draw pattern."

בדקו את התוצאה:

Particle Lifecycle — חלקיקים שנולדים ומתים

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

class LifecycleParticle {
    constructor(x, y) {
        this.x = x;
        this.y = y;
        this.vx = (Math.random() - 0.5) * 6;
        this.vy = (Math.random() - 0.5) * 6;
        this.radius = Math.random() * 4 + 2;
        this.life = 1.0;                      // 1 = מלא, 0 = מת
        this.decay = Math.random() * 0.02 + 0.01;  // מהירות המוות
        this.color = `hsl(${Math.random() * 60 + 10}, 100%, 60%)`;  // גוונים חמים
    }

    update() {
        this.x += this.vx;
        this.y += this.vy;
        this.vy += 0.05;      // gravity — נופל למטה
        this.life -= this.decay;
        this.vx *= 0.99;      // friction — מאט
    }

    draw(ctx) {
        if (this.life <= 0) return;
        ctx.beginPath();
        ctx.arc(this.x, this.y, this.radius * this.life, 0, Math.PI * 2);
        ctx.fillStyle = this.color;
        ctx.globalAlpha = this.life;
        ctx.fill();
        ctx.globalAlpha = 1;
    }

    isDead() {
        return this.life <= 0;
    }
}

// שימוש:
let sparks = [];

canvas.addEventListener('click', (e) => {
    const rect = canvas.getBoundingClientRect();
    const x = e.clientX - rect.left;
    const y = e.clientY - rect.top;
    // יוצרים 50 ניצוצות בנקודת הקליק
    for (let i = 0; i < 50; i++) {
        sparks.push(new LifecycleParticle(x, y));
    }
});

function animate() {
    ctx.clearRect(0, 0, canvas.width, canvas.height);

    sparks.forEach(p => { p.update(); p.draw(ctx); });
    sparks = sparks.filter(p => !p.isDead());  // מסירים מתים

    requestAnimationFrame(animate);
}

הדבר הקריטי כאן: sparks.filter(p => !p.isDead()). בכל frame מסירים חלקיקים שמתו — בלי זה, המערך גדל ללא הגבלה והביצועים נפגעים. כלל האצבע: תמיד נקו חלקיקים מתים.

Framework: אנטומיה של מערכת חלקיקים
רכיבתפקידדוגמאות
Emitter (פולט)מאיפה נולדים חלקיקים — נקודה, קו, או שטחמרכז המסך, מיקום העכבר, נקודת קליק, קצה עליון
Particle (חלקיק)אובייקט בודד עם מיקום, מהירות, גודל, צבע, lifeעיגול, ריבוע, כוכב, פתית שלג, ניצוץ
Forces (כוחות)מה משפיע על תנועת החלקיקgravity (למטה), wind (צידה), friction (האטה), attraction (משיכה לנקודה)
Lifecycle (מחזור חיים)נולד עם life=1, מת כש-life=0fade out, shrink, color change לפני מוות
Connections (חיבורים)קווים בין חלקיקים קרוביםnetwork lines, constellation effect
Interaction (אינטראקציה)תגובה לעכבר, מגע, או אירועיםrepel, attract, spawn on click, follow mouse

כלל אצבע: כשמתארים מערכת חלקיקים ל-AI, ציינו את כל 6 הרכיבים האלה. ככל שתהיו יותר ספציפיים — התוצאה תהיה יותר מדויקת.

Performance: כמה חלקיקים זה יותר מדי?

בלי חיבורי קווים: 1,000-5,000 חלקיקים רצים חלק ברוב המכשירים. עם חיבורי קווים: 100-200 חלקיקים, כי הבדיקה היא O(n²) — 200 חלקיקים = 19,900 בדיקות מרחק כל frame. עם חיבורי קווים + mouse interaction: 80-150 חלקיקים. תמיד בדקו על mobile עם CPU throttling 4x ב-DevTools. אם ה-framerate יורד מתחת ל-30fps — הפחיתו חלקיקים.

מתחיל 8 דקות כלי

10.5 Confetti — הדרך הקלה ביותר להוסיף חגיגה

אם מערכת חלקיקים מאפס מרגישה מורכבת — יש קיצור דרך מושלם. canvas-confetti היא ספרייה קטנטנה (2.7KB gzipped, zero dependencies) שנותנת לכם אפקט קונפטי מקצועי בשורה אחת של קוד. היא כל כך פופולרית (12M+ downloads בשבוע) שהיא כמעט סטנדרט — כל פעם שאתם רואים קונפטי באתר, סביר שזו הספרייה הזו.

// שלב 1: הוספה (CDN)
// <script src="https://cdn.jsdelivr.net/npm/canvas-confetti@1.9.3/dist/confetti.browser.min.js"></script>

// שלב 2: הפעלה — שורה אחת!
confetti();

// זהו. קונפטי. עכשיו בואו נתאים אישית:

התאמות אישיות

// Burst קלאסי
confetti({
    particleCount: 100,
    spread: 70,
    origin: { y: 0.6 }       // מאיפה יוצא — 0.6 = 60% מלמעלה
});

// Fireworks — זיקוקים
confetti({
    particleCount: 200,
    spread: 360,              // 360 מעלות — כל הכיוונים
    startVelocity: 30,
    gravity: 0.5,
    ticks: 200,               // כמה frames עד שהחלקיקים נעלמים
    origin: { x: 0.5, y: 0.3 }
});

// גשם קונפטי — נופל מלמעלה
confetti({
    particleCount: 50,
    angle: 270,               // כלפי מטה
    spread: 60,
    origin: { x: 0.5, y: 0 }, // מהקצה העליון
    gravity: 1.5,
    drift: 0.5                // סחף הצידה
});

// צבעים מותאמים
confetti({
    particleCount: 100,
    spread: 70,
    colors: ['#ff0000', '#00ff00', '#0000ff'],  // רק 3 צבעים
    shapes: ['circle', 'square'],               // רק עיגולים וריבועים
    scalar: 1.2                                  // גודל חלקיקים
});

// כוכבים!
confetti({
    particleCount: 50,
    spread: 60,
    shapes: ['star'],
    colors: ['#ffd700', '#ffaa00', '#ff8800'],
    scalar: 2
});

Triggers נפוצים

// 1. כפתור הצלחה
document.getElementById('buyBtn').addEventListener('click', () => {
    confetti({ particleCount: 150, spread: 90, origin: { y: 0.7 } });
});

// 2. סיום טופס
form.addEventListener('submit', () => {
    confetti({ particleCount: 200, spread: 120 });
});

// 3. Scroll milestone — הגיע לסוף הדף
const observer = new IntersectionObserver(entries => {
    entries.forEach(entry => {
        if (entry.isIntersecting) {
            confetti({ particleCount: 100, spread: 70 });
            observer.unobserve(entry.target);  // פעם אחת בלבד
        }
    });
});
observer.observe(document.getElementById('footer'));

// 4. קונפטי מתמשך (cannon שמאל + cannon ימין)
function fireCannons() {
    confetti({ angle: 60, spread: 55, particleCount: 40, origin: { x: 0 } });
    confetti({ angle: 120, spread: 55, particleCount: 40, origin: { x: 1 } });
}
// מפעילים 5 פעמים עם delay
let count = 0;
const interval = setInterval(() => {
    fireCannons();
    if (++count >= 5) clearInterval(interval);
}, 250);
3 דקות עשו עכשיו: קונפטי ב-30 שניות
  1. פתחו CodePen חדש
  2. ב-Settings → JS הוסיפו External Script: https://cdn.jsdelivr.net/npm/canvas-confetti@1.9.3/dist/confetti.browser.min.js
  3. ב-HTML: <button onclick="confetti({particleCount:200, spread:100})">Party!</button>
  4. לחצו על הכפתור. קונפטי!
  5. שנו את spread ל-360 ו-shapes ל-['star'] ותראו את ההבדל
2 דקות עשו עכשיו: מצאו את הקונפטי הבא בפרויקט שלכם
  1. חשבו על הפרויקט שאתם בונים — היכן רגע של "חגיגה"?
  2. סיום רכישה? סיום הרשמה? הגעה ל-milestone? שליחת טופס?
  3. רשמו מקום אחד שבו תוסיפו confetti — ואיזה סגנון (burst, fireworks, rain, stars)
בינוני 10 דקות יצירתי

10.6 אמנות גנרטיבית — אקראיות, רעש, p5.js ורקעים פרוצדורליים

אמנות גנרטיבית (generative art) היא אמנות שנוצרת על ידי אלגוריתם. אתם כותבים כללים — וכל ריצה יוצרת תוצאה שונה. זה לא רק "יפה" — זה שימושי מאוד לאתרים: רקעים ייחודיים שמשתנים בכל טעינה, patterns שנראים אורגניים, טקסטורות שלא חוזרות על עצמן. ו-Canvas הוא הכלי הטבעי ליצירת אמנות גנרטיבית.

רנדומיות חכמה — לא סתם Math.random()

Math.random() נותן ערכים "קופצניים" — אין קשר בין ערך אחד לבא אחריו. זה טוב למיקום התחלתי של חלקיקים, אבל גרוע לתנועה או טקסטורות. לזה יש פתרון: Perlin Noise.

Perlin noise (שנוצר על ידי Ken Perlin ב-1983 לסרט Tron) מייצר ערכים "חלקים" — כל ערך קרוב לשכנים שלו, מה שיוצר תנועה אורגנית וטקסטורות טבעיות. תחשבו על ההבדל בין רעש סטטי בטלוויזיה (Math.random) לבין גבעות מתגלגלות (Perlin noise) — שניהם אקראיים, אבל Perlin noise "חלק".

ב-Canvas נקי אין Perlin noise מובנה. לכן, בפרויקטים מקצועיים כמעט תמיד משתמשים ב-p5.js — ספרייה ל-creative coding שעוטפת את Canvas API ומוסיפה:

דוגמה: Flow Field — שדה זרימה

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

// p5.js — flow field
function setup() {
    createCanvas(800, 600);
    background(15, 23, 42);          // רקע כהה
    noLoop();                         // רץ פעם אחת
}

function draw() {
    const scale = 0.005;              // גודל ה-noise pattern
    const stepLength = 3;
    const numLines = 2000;

    for (let i = 0; i < numLines; i++) {
        let x = random(width);
        let y = random(height);
        let hue = map(x, 0, width, 200, 300);  // כחול → סגול

        stroke(hue, 80, 70, 15);     // HSB color עם שקיפות נמוכה
        strokeWeight(1);
        noFill();
        beginShape();

        for (let j = 0; j < 80; j++) {
            vertex(x, y);
            const angle = noise(x * scale, y * scale) * TWO_PI * 2;
            x += cos(angle) * stepLength;
            y += sin(angle) * stepLength;

            // עוצרים אם יוצאים מהגבולות
            if (x < 0 || x > width || y < 0 || y > height) break;
        }
        endShape();
    }
}

2,000 קווים שזורמים לפי שדה noise — ואם תריצו שוב, תקבלו תוצאה אחרת לגמרי. זה הקסם של generative art.

5 דקות עשו עכשיו: אמנות גנרטיבית בדקה
  1. פתחו את p5.js Web Editor
  2. מחקו את הקוד הקיים והדביקו:
    function setup() { createCanvas(600, 400); background(20); noStroke(); }
    function draw() {
    for (let i = 0; i < 5; i++) {
    let x = random(width), y = random(height);
    let s = noise(x*0.01, y*0.01, frameCount*0.01) * 30;
    fill(noise(x*0.01)*255, 100, 200, 30);
    ellipse(x, y, s, s);
    }
    }
  3. לחצו Play — תראו ציור שנבנה לאט לאט, עם עיגולים שנשלטים על ידי noise
  4. שנו את 0.01 ל-0.05 ותראו איך ה-noise pattern משתנה

רקעים פרוצדורליים לאתרים

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

כ-Vibe Coders, כנראה לא תכתבו generative art מאפס. אבל חשוב שתכירו את המושגים — כדי לדעת מה לבקש מ-AI. ההבדל בין "תעשה רקע מגניב" לבין "Create a Canvas background with Perlin noise-driven flowing lines in shades of blue-purple, low opacity, regenerated on each page load" — זה ההבדל בין תוצאה גנרית לתוצאה מרשימה.

10 דקות תרגיל: רקע גנרטיבי לפרויקט

בקשו מ-AI רקע גנרטיבי באחד הסגנונות הבאים. בחרו את מה שמתאים לפרויקט שלכם:

  1. Network dots: "Create a subtle Canvas background with dots arranged in a grid, where dot size varies using Perlin noise. Colors: dark blue to purple gradient. Animate slowly."
  2. Flowing lines: "Create a Canvas generative art background with flowing curves using Perlin noise flow field. Colors: muted blues and greens on dark background. Regenerate on each page load."
  3. Organic blobs: "Create animated Canvas background with 3-4 large soft blobs that slowly morph using noise-based vertex displacement. Pastel colors with low opacity."

בדקו: האם ה-AI יצר רקע שמשתנה בכל טעינה? האם הוא עדין מספיק שלא מפריע לתוכן? האם הוא רספונסיבי?

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

10.7 גרדיאנטים דינמיים ורקעים שנושמים

אחד השימושים הנפוצים ביותר של Canvas באתרים מקצועיים הוא רקעים דינמיים — גרדיאנטים שזוחלים, אורורה שנושמת, mesh gradients שמשתנים. אלה אפקטים שאפשר לעשות חלקית עם CSS (background animation) — אבל Canvas נותן שליטה מלאה וביצועים טובים יותר.

Animated Gradient — הבסיס

let time = 0;

function animate() {
    time += 0.005;

    // צבעים שמשתנים לאט
    const r1 = Math.sin(time) * 50 + 100;
    const g1 = Math.sin(time * 0.7) * 50 + 50;
    const b1 = Math.sin(time * 1.3) * 50 + 150;

    const r2 = Math.sin(time * 0.5 + 2) * 50 + 80;
    const g2 = Math.sin(time * 0.8 + 1) * 50 + 30;
    const b2 = Math.sin(time * 1.1 + 3) * 50 + 180;

    const gradient = ctx.createLinearGradient(
        Math.sin(time * 0.3) * canvas.width * 0.5 + canvas.width * 0.25, 0,
        Math.cos(time * 0.3) * canvas.width * 0.5 + canvas.width * 0.75, canvas.height
    );

    gradient.addColorStop(0, `rgb(${r1}, ${g1}, ${b1})`);
    gradient.addColorStop(1, `rgb(${r2}, ${g2}, ${b2})`);

    ctx.fillStyle = gradient;
    ctx.fillRect(0, 0, canvas.width, canvas.height);

    requestAnimationFrame(animate);
}

animate();

הטריק: Math.sin(time) ו-Math.cos(time) עם frequencies שונים לכל ערך. כש-time עולה לאט (0.005 כל frame), הצבעים "נושמים" — משתנים בצורה חלקה ומעגלית, בלי לחזור על אותו דפוס.

Aurora Effect — אורורה

אפקט אורורה (Northern Lights) הוא אחד האפקטים הפופולריים ביותר באתרי פרימיום. הרעיון: כמה שכבות של גרדיאנטים עם צבעים שונים שנעים במהירויות שונות:

function drawAurora(time) {
    ctx.clearRect(0, 0, canvas.width, canvas.height);

    // רקע כהה
    ctx.fillStyle = '#0a0a1a';
    ctx.fillRect(0, 0, canvas.width, canvas.height);

    // שכבות אורורה
    const layers = [
        { color: 'rgba(0, 255, 128, 0.08)', speed: 0.3, amplitude: 100, yOffset: 0.3 },
        { color: 'rgba(0, 128, 255, 0.06)', speed: 0.5, amplitude: 80, yOffset: 0.4 },
        { color: 'rgba(128, 0, 255, 0.05)', speed: 0.7, amplitude: 120, yOffset: 0.5 },
        { color: 'rgba(0, 255, 200, 0.04)', speed: 0.2, amplitude: 60, yOffset: 0.35 },
    ];

    layers.forEach(layer => {
        ctx.beginPath();
        ctx.moveTo(0, canvas.height);

        for (let x = 0; x <= canvas.width; x += 5) {
            const y = canvas.height * layer.yOffset
                + Math.sin(x * 0.003 + time * layer.speed) * layer.amplitude
                + Math.sin(x * 0.007 - time * layer.speed * 0.5) * layer.amplitude * 0.5;
            ctx.lineTo(x, y);
        }

        ctx.lineTo(canvas.width, 0);
        ctx.lineTo(0, 0);
        ctx.closePath();

        // Gradient fill מלמעלה לקו
        const grad = ctx.createLinearGradient(0, 0, 0, canvas.height * layer.yOffset + layer.amplitude);
        grad.addColorStop(0, 'transparent');
        grad.addColorStop(0.5, layer.color);
        grad.addColorStop(1, 'transparent');
        ctx.fillStyle = grad;
        ctx.fill();
    });
}

let t = 0;
function animate() {
    t += 0.01;
    drawAurora(t);
    requestAnimationFrame(animate);
}
animate();

4 שכבות, כל אחת עם צבע שונה, מהירות שונה, ומשרעת שונה. ביחד הן יוצרות אפקט שנראה כמו אורות צפון — אורגני, עדין, ומהפנט. הטריק: rgba עם opacity נמוכה (0.04-0.08) — שכבות שקופות שנערמות ומייצרות עומק.

Mesh Gradient — הטרנד של 2025-2026

Mesh gradients — גרדיאנטים עם נקודות שליטה מרובות שיוצרים מעברי צבע מורכבים — הם הטרנד הויזואלי הנוכחי. אתם רואים אותם באתרים של Apple, Linear, Vercel, ו-Stripe. ב-Canvas, הדרך ליצור אותם:

function drawMeshGradient(time) {
    ctx.clearRect(0, 0, canvas.width, canvas.height);

    // נקודות שליטה שנעות
    const blobs = [
        {
            x: canvas.width * 0.3 + Math.sin(time * 0.5) * 100,
            y: canvas.height * 0.3 + Math.cos(time * 0.3) * 80,
            color: 'rgba(99, 102, 241, 0.6)',    // indigo
            radius: 250
        },
        {
            x: canvas.width * 0.7 + Math.cos(time * 0.4) * 120,
            y: canvas.height * 0.5 + Math.sin(time * 0.6) * 90,
            color: 'rgba(236, 72, 153, 0.5)',    // pink
            radius: 300
        },
        {
            x: canvas.width * 0.5 + Math.sin(time * 0.7) * 80,
            y: canvas.height * 0.7 + Math.cos(time * 0.5) * 100,
            color: 'rgba(59, 130, 246, 0.5)',    // blue
            radius: 280
        }
    ];

    blobs.forEach(blob => {
        const grad = ctx.createRadialGradient(
            blob.x, blob.y, 0,
            blob.x, blob.y, blob.radius
        );
        grad.addColorStop(0, blob.color);
        grad.addColorStop(1, 'transparent');
        ctx.fillStyle = grad;
        ctx.fillRect(0, 0, canvas.width, canvas.height);
    });

    // Blur effect דרך filter
    ctx.filter = 'blur(60px)';
    ctx.drawImage(canvas, 0, 0);
    ctx.filter = 'none';
}

3 "כתמי צבע" רדיאליים שנעים בנתיבים שונים, עם blur שממיס אותם יחד. התוצאה: mesh gradient דינמי שמשתנה כל הזמן. אפשר להוסיף עוד blobs, לשנות צבעים, ולשחק עם ה-radius וה-opacity.

5 דקות עשו עכשיו: רקע דינמי עם AI
  1. בחרו אחד מהאפקטים: animated gradient, aurora, או mesh gradient
  2. בקשו מ-AI (Cursor / Claude / v0): "Create a fullscreen Canvas animated [gradient/aurora/mesh gradient] background with smooth color transitions. Dark theme, slow movement. Canvas should resize with window. Use requestAnimationFrame."
  3. שלבו את הקנבס כ-background: position: fixed; top: 0; left: 0; z-index: -1
  4. בדקו שהתוכן מעליו קריא — אם לא, הוסיפו שכבת overlay: background: rgba(0,0,0,0.5)
מתקדם 10 דקות טכניקה

10.8 Canvas + Scroll — אנימציות מונעות גלילה וסדרות פריימים

זוכרים את ScrollTrigger מפרק 5? עכשיו שאתם מכירים Canvas, יש טכניקה חדשה שמשלבת את שניהם: scroll-driven Canvas. במקום לחבר את הגלילה לאנימציות CSS או GSAP, אתם מחברים אותה ישירות לציור ב-Canvas. התוצאה: אפקטים שלא ניתנים להשגה בשום דרך אחרת.

Frame Sequence — הטכניקה של Apple

הטכניקה הידועה ביותר: frame sequence. Apple השתמשו בה באתר של AirPods Pro, MacBook Pro, ו-iPhone — גוללים, ומוצר תלת-ממדי מסתובב בצורה חלקה. איך? מאות תמונות (frames) שנטענות מראש, ובכל מיקום scroll מציגים frame אחר:

const frameCount = 148;               // 148 תמונות (0001.jpg עד 0148.jpg)
const images = [];
let currentFrame = 0;

// טעינת כל התמונות מראש
for (let i = 1; i <= frameCount; i++) {
    const img = new Image();
    img.src = `/frames/${String(i).padStart(4, '0')}.jpg`;
    images.push(img);
}

// חיבור לגלילה
window.addEventListener('scroll', () => {
    const scrollTop = window.scrollY;
    const maxScroll = document.body.scrollHeight - window.innerHeight;
    const scrollFraction = scrollTop / maxScroll;
    const frameIndex = Math.min(
        frameCount - 1,
        Math.floor(scrollFraction * frameCount)
    );

    if (frameIndex !== currentFrame) {
        currentFrame = frameIndex;
        // מציירים את הפריים הנוכחי
        ctx.clearRect(0, 0, canvas.width, canvas.height);
        if (images[currentFrame].complete) {
            ctx.drawImage(images[currentFrame], 0, 0, canvas.width, canvas.height);
        }
    }
});

// ציור הפריים הראשון כשהוא מוכן
images[0].onload = () => {
    ctx.drawImage(images[0], 0, 0, canvas.width, canvas.height);
};

הגולל/ת שולט/ת באנימציה — כל מיקום scroll מראה frame אחר. עם 148 frames זה 148 "תמונות" של המוצר מזוויות שונות — מה שנראה כמו סיבוב תלת-ממדי חלק. הטריק: Canvas מצייר תמונה (drawImage) — הרבה יותר מהיר מהחלפת src של <img>.

Frame sequences = הרבה בייטים

148 תמונות × 50KB כל אחת = 7.4MB שצריך להוריד. זה כבד. פתרונות: (1) דחיסה אגרסיבית — WebP או AVIF יכולים להוריד ל-20KB לתמונה. (2) Progressive loading — מתחילים עם 30 frames ברזולוציה נמוכה, טוענים את הנותרים ברקע. (3) ב-mobile אפשר להסתפק ב-60 frames במקום 148. (4) שקלו אם video element עם מניפולציית currentTime לא עדיף — פחות bandwidth, אותה תוצאה.

Scroll-Driven Canvas Drawing

אפשרות פשוטה יותר: במקום סדרת תמונות, מציירים ב-Canvas לפי מיקום הגלילה. למשל — path drawing שמתקדם עם הגלילה:

// קו שמצטייר ככל שגוללים
window.addEventListener('scroll', () => {
    const scrollFraction = window.scrollY / (document.body.scrollHeight - window.innerHeight);

    ctx.clearRect(0, 0, canvas.width, canvas.height);

    // נתיב מוגדר מראש
    const points = [
        [100, 100], [200, 150], [300, 80], [400, 200],
        [500, 120], [600, 180], [700, 100]
    ];

    // מציירים רק את החלק שמתאים ל-scroll
    const totalLength = points.length - 1;
    const currentLength = scrollFraction * totalLength;

    ctx.beginPath();
    ctx.moveTo(points[0][0], points[0][1]);

    for (let i = 1; i <= Math.floor(currentLength) && i < points.length; i++) {
        ctx.lineTo(points[i][0], points[i][1]);
    }

    // interpolation לנקודה האחרונה (חלק מקטע)
    const frac = currentLength - Math.floor(currentLength);
    const idx = Math.floor(currentLength);
    if (idx < points.length - 1) {
        const x = points[idx][0] + (points[idx + 1][0] - points[idx][0]) * frac;
        const y = points[idx][1] + (points[idx + 1][1] - points[idx][1]) * frac;
        ctx.lineTo(x, y);
    }

    ctx.strokeStyle = '#3b82f6';
    ctx.lineWidth = 3;
    ctx.stroke();
});

השילוב של Canvas עם GSAP ScrollTrigger אפילו חזק יותר — ScrollTrigger מנהל את ה-scroll state ו-Canvas מצייר. הנה פרומפט שיעזור לכם:

15 דקות תרגיל: Scroll-Driven Canvas

בחרו אחד מהשילובים הבאים ובקשו מ-AI:

  1. Particle density: "Canvas particles background where particle count increases as user scrolls down. Start with 20 particles at top, reach 200 at bottom. Use GSAP ScrollTrigger scrub to control particle count. Dark background."
  2. Canvas path drawing: "SVG-like path drawing on Canvas that draws progressively as user scrolls. Create an artistic curved line path across the screen. Use scroll position (0-1) to control how much of the path is drawn."
  3. Color transition: "Fullscreen Canvas gradient background that transitions from deep blue (top of page) to purple (middle) to dark pink (bottom) based on scroll position. Smooth interpolation."

בדקו: הגלילה חלקה? האנימציה לא "קופצת" בין מצבים? האם היא עובדת גם למעלה וגם למטה (bidirectional)?

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

10.9 ביצועים — Offscreen Canvas, Pixel Ratio, ומתי לעצור

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

Device Pixel Ratio — למה הציור מטושטש

הבעיה הנפוצה ביותר: Canvas נראה מטושטש במסכי Retina. הסיבה: מסך Retina הוא 2x (או 3x) — כלומר כל "פיקסל CSS" הוא בעצם 2×2 (או 3×3) פיקסלים פיזיים. אם Canvas שלכם 800×600, ב-Retina הוא נמתח ל-1600×1200 — ומיטשטש.

הפתרון:

function setupCanvas(canvas) {
    const dpr = window.devicePixelRatio || 1;
    const rect = canvas.getBoundingClientRect();

    // Canvas אמיתי גדול יותר
    canvas.width = rect.width * dpr;
    canvas.height = rect.height * dpr;

    // תצוגה בגודל רגיל
    canvas.style.width = rect.width + 'px';
    canvas.style.height = rect.height + 'px';

    // סקיילינג של ה-context
    const ctx = canvas.getContext('2d');
    ctx.scale(dpr, dpr);

    return ctx;
}

עכשיו Canvas ברזולוציה כפולה — חד כמו שאר הדף. תמיד עשו את זה — זה 5 שורות שהופכות את הציור מבינוני למושלם.

Offscreen Canvas — חישובים ברקע

Offscreen Canvas הוא Canvas שלא מוצג על המסך — משמש לחישובים מקדימים (pre-rendering) או עבודה ב-Web Worker:

// שימוש 1: Pre-rendering — מציירים אובייקט מורכב פעם אחת
const offscreen = document.createElement('canvas');
offscreen.width = 100;
offscreen.height = 100;
const offCtx = offscreen.getContext('2d');

// מציירים חלקיק מורכב פעם אחת
offCtx.beginPath();
offCtx.arc(50, 50, 40, 0, Math.PI * 2);
const grad = offCtx.createRadialGradient(50, 50, 0, 50, 50, 40);
grad.addColorStop(0, 'rgba(255, 255, 255, 0.8)');
grad.addColorStop(1, 'rgba(255, 255, 255, 0)');
offCtx.fillStyle = grad;
offCtx.fill();

// ב-animation loop — פשוט מדביקים את התמונה (הרבה יותר מהיר!)
particles.forEach(p => {
    ctx.drawImage(offscreen, p.x - 50, p.y - 50);
});

במקום לצייר גרדיאנט רדיאלי 500 פעם כל frame (פעם לכל חלקיק), מציירים אותו פעם אחת על offscreen canvas ואז פשוט מדביקים (drawImage). ההבדל: מ-8ms ל-1ms per frame.

כללי ביצועים

Framework: Canvas Performance Rules
כלללמהאיך
devicePixelRatioציור חד ב-Retinacanvas.width = rect.width * dpr; ctx.scale(dpr, dpr)
Batch draw callsכל beginPath → fill הוא קריאה יקרהצרו path אחד עם כל הנקודות, fill פעם אחת
Offscreen canvasPre-render אובייקטים מורכביםציירו פעם אחת → drawImage בכל frame
Limit particle countכל חלקיק = חישוב + ציורDesktop: 200-500 (עם קווים), 2000+ (בלי). Mobile: חצי
Clean dead particlesמערך שגדל = memory leakfilter() כל frame, או object pool
Pause when invisibleלא שורפים CPU ברקעIntersection Observer → cancelAnimationFrame
Throttle mouse eventsmousemove fires מאות פעמים בשנייהשמרו מיקום, קראו רק ב-animation loop
Avoid ctx.filterblur() על canvas כבד מאודהשתמשו ב-CSS filter על ה-canvas element במקום

מתי לעצור — Pause כשלא רואים

Canvas animation שרצה ברקע (בטאב לא פעיל, או מתחת ל-fold) שורפת CPU וסוללה בחינם. requestAnimationFrame כבר נעצר כשהטאב לא פעיל, אבל מה עם Canvas שנגלל מחוץ למסך?

let animationId;
let isVisible = false;

const observer = new IntersectionObserver(entries => {
    entries.forEach(entry => {
        if (entry.isIntersecting) {
            if (!isVisible) {
                isVisible = true;
                animate();  // מתחילים
            }
        } else {
            isVisible = false;
            cancelAnimationFrame(animationId);  // עוצרים
        }
    });
}, { threshold: 0.1 });

observer.observe(canvas);

function animate() {
    if (!isVisible) return;
    ctx.clearRect(0, 0, canvas.width, canvas.height);
    // ... update + draw ...
    animationId = requestAnimationFrame(animate);
}

Intersection Observer בודק אם Canvas נראה על המסך — אם לא, עוצרים. שימו לב ל-threshold: 0.1 — מפעילים כשלפחות 10% מהקנבס נראה. זה קריטי במיוחד ל-mobile שם חיי סוללה קריטיים.

3 דקות עשו עכשיו: בדקו ביצועי Canvas
  1. פתחו את ה-CodePen עם ה-particle network שבניתם
  2. פתחו Chrome DevTools → Performance tab → Record
  3. הריצו 3 שניות → Stop. חפשו את ה-FPS counter למעלה
  4. ירוק = 60fps (מצוין). צהוב = 30-50fps (בסדר). אדום = מתחת ל-30fps (בעיה)
  5. אם אדום: הפחיתו particle count או הסירו חיבורי קווים
מתחיל 6 דקות החלטה

10.10 Canvas vs SVG vs CSS — איך להחליט

אחרי שלמדתם CSS animations (פרק 2), SVG (פרק 8), ועכשיו Canvas — הגיע הזמן לתמונה המלאה. מתי כל טכנולוגיה? הנה Framework ההחלטה:

Framework: Canvas vs SVG vs CSS — טבלת החלטה
קריטריוןCSSSVGCanvas
כמות אובייקטים1-2010-300100-100,000+
סוג אובייקטיםאלמנטי HTML קיימיםצורות וקטוריות (path, circle)פיקסלים — כל דבר
Scalabilityכמו HTMLחד בכל גודל (vector)מיטשטש בהגדלה (pixel) — צריך dpr
Accessibilityמלאה (DOM)טובה (DOM + aria)אפס (צריך aria-label על ה-canvas)
Event handlingמובנה (click, hover)מובנה על כל elementידני — צריך hit detection
Text renderingמצויןטובבסיסי (אין word-wrap, אין rich text)
Performanceטוב למעטבינוני (DOM overhead)מצוין להרבה
Learning curveקלהבינוניתתלולה
DevTools supportמלאמלא (inspect elements)מינימלי (רואים bitmap)
SEOמלאטוב (text ב-SVG ניתן לאינדוקס)אפס (הכל פיקסלים)

כללי אצבע מהירים:

זכרו: אין תשובה "נכונה" אחת. הרבה אפקטים אפשר לבנות בכמה טכנולוגיות — הבחירה תלויה בהקשר. particles background על hero section? Canvas. אותם particles כאנימציית Lottie מוכנה? גם אפשר — יותר קל אבל פחות גמיש. השאלה היא תמיד: כמה שליטה צריך וכמה אובייקטים.

מתחיל 6 דקות AI

10.11 חמישה פרומפטים AI לאפקטי Canvas

כ-Vibe Coders, הכוח שלכם הוא לדעת מה לבקש. עכשיו שאתם מכירים את המושגים — particles, animation loop, Perlin noise, offscreen canvas, devicePixelRatio — הפרומפטים שלכם ל-AI יהיו מדויקים בהרבה. הנה 5 פרומפטים מוכנים לשימוש:

פרומפט 1: Particle Network Background

"Create a fullscreen Canvas 2D particle network background. Requirements: (1) 120 white particles on dark background (#0f172a), (2) particles connected by lines when distance < 120px — line opacity fades with distance, (3) particles wrap around edges, (4) mouse interaction — particles push away within 150px radius, (5) devicePixelRatio support for sharp Retina rendering, (6) canvas resizes with window, (7) pause animation when canvas not visible using IntersectionObserver. Use requestAnimationFrame with clear-update-draw pattern. Position canvas with position:fixed, z-index:-1 as page background."

פרומפט 2: Confetti Celebration

"Add canvas-confetti celebration effects to my page. Include: (1) Burst confetti on button click with 150 particles, spread 90, origin at button position, (2) Side cannons effect (left + right simultaneously) for form submission, (3) Star-shaped confetti in gold colors for premium/success moments, (4) Realistic fireworks effect that fires 3 times with 200ms delay. Use the canvas-confetti CDN library. Show me the HTML button + JS for each trigger."

פרומפט 3: Animated Aurora Background

"Create a Canvas 2D aurora/northern lights background effect. Requirements: (1) dark background (#0a0a1a), (2) 4-5 semi-transparent color layers (greens, blues, purples) that wave independently, (3) each layer uses sine waves with different frequencies and amplitudes, (4) layers have gradient fills from transparent to color to transparent, (5) slow, hypnotic movement — speed around 0.01 per frame, (6) fullscreen, resizes with window, (7) devicePixelRatio support. Use requestAnimationFrame. Should feel calm and premium, not busy."

פרומפט 4: Scroll-Driven Canvas

"Create a scroll-driven Canvas effect where particles increase in density as user scrolls down the page. At the top: 10 scattered particles. At the bottom: 300 connected particles forming a dense network. Use scroll position (0 to 1) to interpolate particle count and connection distance. Canvas should be pinned (position: sticky or fixed) as fullscreen background. Include smooth interpolation so changes feel gradual. Use GSAP ScrollTrigger if available, otherwise plain scroll event."

פרומפט 5: Generative Art Background

"Create a generative art Canvas background using Perlin noise flow field. Requirements: (1) dark background, (2) 1000-2000 short curved lines that follow a noise-based direction field, (3) color palette: muted blues to purples (hue 200-280), low opacity (0.1-0.2), (4) regenerate on each page load with different seed, (5) static — draws once on load, no animation needed, (6) fullscreen, devicePixelRatio aware. Use a noise library (simplex-noise npm or p5.js noise function). The result should look like an abstract organic pattern — similar to generative art prints."
5 דקות עשו עכשיו: נסו פרומפט
  1. בחרו את הפרומפט שהכי מתאים לפרויקט שלכם
  2. העתיקו אותו ל-AI (Cursor / Claude / v0 / Bolt)
  3. בדקו את התוצאה — האם כל הדרישות מולאו?
  4. אם משהו לא בסדר — ציינו בדיוק מה: "the particles don't connect with lines" או "missing devicePixelRatio support — looks blurry on Retina"
  5. שימו לב איך מונחים מדויקים (כמו "devicePixelRatio" או "IntersectionObserver for pause") מייצרים תוצאה מדויקת יותר
מתקדם 10 דקות תבניות

10.12 תבניות מקצועיות — Particles, Data Vis, Image Effects וסיפורי גלילה

עכשיו שיש לכם את הבסיס, בואו נראה 4 שימושים מקצועיים של Canvas באתרים אמיתיים — תבניות שתוכלו לבקש מ-AI ולשלב בפרויקטים.

תבנית 1: Particle Background עם Brand Colors

ה-particle network שבניתם לפני כמה סעיפים — עכשיו בגרסה מקצועית שמשתלבת עם brand:

// Particle background — גרסה מקצועית
const config = {
    particleCount: 80,
    maxDistance: 120,
    particleColor: 'rgba(99, 102, 241, 0.6)',    // Brand color (indigo)
    lineColor: 'rgba(99, 102, 241, 0.15)',
    mouseRadius: 200,
    speed: 0.3,
    particleSize: { min: 1, max: 3 },
    responsive: {
        mobile: { particleCount: 40, maxDistance: 80 },
        tablet: { particleCount: 60, maxDistance: 100 }
    }
};

// Responsive — מתאימים למכשיר
function getConfig() {
    const width = window.innerWidth;
    if (width < 768) return { ...config, ...config.responsive.mobile };
    if (width < 1024) return { ...config, ...config.responsive.tablet };
    return config;
}

שימו לב ל-responsive config — ב-mobile מפחיתים חלקיקים ומרחק חיבור. זה ההבדל בין "דמו שעובד" ל"פיצ'ר production".

תבנית 2: Canvas Data Visualization

כש-Chart.js או D3.js כבדים מדי, Canvas ישיר מספיק לויזואליזציה קלה:

// Bar chart פשוט ואנימטיבי ב-Canvas
function drawAnimatedBars(data, progress) {
    const barWidth = canvas.width / data.length * 0.7;
    const gap = canvas.width / data.length * 0.3;
    const maxVal = Math.max(...data.map(d => d.value));

    data.forEach((item, i) => {
        const x = i * (barWidth + gap) + gap / 2;
        const targetHeight = (item.value / maxVal) * canvas.height * 0.8;
        const currentHeight = targetHeight * easeOutCubic(progress);  // אנימציה
        const y = canvas.height - currentHeight;

        // גרדיאנט לכל bar
        const grad = ctx.createLinearGradient(x, y, x, canvas.height);
        grad.addColorStop(0, item.color);
        grad.addColorStop(1, item.colorDark);
        ctx.fillStyle = grad;

        // Rounded corners
        roundRect(ctx, x, y, barWidth, currentHeight, 8);
        ctx.fill();

        // Label
        ctx.fillStyle = '#ffffff';
        ctx.font = '14px Inter';
        ctx.textAlign = 'center';
        ctx.fillText(item.label, x + barWidth / 2, canvas.height - 10);
    });
}

function easeOutCubic(t) {
    return 1 - Math.pow(1 - t, 3);
}

כל הביצועים של Canvas, עם אנימציית כניסה חלקה. מתאים ל-dashboards, landing pages, ו-data stories.

תבנית 3: Image Pixel Effects

Canvas יכול לגשת לפיקסלים של תמונה ולעשות איתם דברים — dissolve, pixelate, color shift, particle explosion:

// Pixelate effect — תמונה שמתפקסלת ומתאוששת
function pixelateImage(image, pixelSize) {
    const w = canvas.width;
    const h = canvas.height;

    // מצמצמים ומגדילים — יוצר אפקט פיקסלציה
    ctx.imageSmoothingEnabled = false;
    ctx.drawImage(image, 0, 0, w / pixelSize, h / pixelSize);
    ctx.drawImage(canvas, 0, 0, w / pixelSize, h / pixelSize, 0, 0, w, h);
    ctx.imageSmoothingEnabled = true;
}

// שימוש: pixelSize גדול = יותר פיקסלי, pixelSize=1 = תמונה רגילה
// אנימציה: מתחילים עם pixelSize=40, מקטינים לאט ל-1
let pixel = 40;
function reveal() {
    ctx.clearRect(0, 0, canvas.width, canvas.height);
    pixelateImage(myImage, Math.max(1, Math.floor(pixel)));
    pixel *= 0.95;  // מקטינים 5% כל frame
    if (pixel > 1.1) requestAnimationFrame(reveal);
    else ctx.drawImage(myImage, 0, 0, canvas.width, canvas.height);  // תמונה חדה
}

אפקט reveal שמתחיל מתמונה מפוקסלת לחלוטין ולאט לאט חושף את המקור. מצוין ל-hero images, product reveals, ו-before/after sections.

תבנית 4: Mouse Trail Effect

אפקט trail — שובל שעוקב אחרי העכבר:

const trail = [];
const TRAIL_LENGTH = 30;

canvas.addEventListener('mousemove', (e) => {
    const rect = canvas.getBoundingClientRect();
    trail.push({
        x: e.clientX - rect.left,
        y: e.clientY - rect.top,
        life: 1.0
    });
    if (trail.length > TRAIL_LENGTH) trail.shift();
});

function drawTrail() {
    trail.forEach((point, i) => {
        const progress = i / trail.length;  // 0 = ישן, 1 = חדש
        const radius = progress * 15 + 2;
        const opacity = progress * 0.6;

        ctx.beginPath();
        ctx.arc(point.x, point.y, radius, 0, Math.PI * 2);
        ctx.fillStyle = `rgba(99, 102, 241, ${opacity})`;
        ctx.fill();
    });
}

function animate() {
    ctx.clearRect(0, 0, canvas.width, canvas.height);
    drawTrail();
    requestAnimationFrame(animate);
}
animate();

עיגולים שהולכים וגדלים מהזנב לראש, עם opacity שעולה — יוצרים "שובל" עדין שעוקב אחרי העכבר. אפקט פופולרי באתרי portfolio ו-creative agencies.

20 דקות תרגיל מסכם: Canvas בפרויקט שלכם

בחרו שניים מבין הרכיבים הבאים ושלבו בפרויקט:

  1. Particle background ל-hero section — עם brand colors ו-mouse interaction
  2. Confetti burst על כפתור CTA או סיום טופס
  3. Animated gradient או aurora כרקע ל-section
  4. Mouse trail שעוקב אחרי העכבר ברחבי הדף

השתמשו ב-AI: קחו את הפרומפטים מסעיף 10.11 כבסיס, התאימו לפרויקט שלכם (צבעים, גדלים, particle count). ודאו שכל רכיב:

צ'קליסט — לפני שמפרסמים אנימציות Canvas
שגרת עבודה: הוספת אפקט Canvas לפרויקט
שלבפעולהזמן
1. החלטהמה האפקט? Particles, confetti, gradient, generative? האם Canvas הכלי הנכון — או SVG/CSS מספיקים?5 דקות
2. פרומפטכתבו פרומפט מדויק ל-AI עם כל הפרטים: particle count, colors, behavior, interactions, device support5 דקות
3. בניהשלבו את הקוד שה-AI ייצר. בדקו שהוא כולל: dpr, resize, clearRect בכל frame15 דקות
4. התאמהשנו צבעים ל-brand, particle count, speed, opacity. בדקו שזה עדין — רקע, לא מרכז תשומת לב10 דקות
5. Responsiveבדקו mobile: הפחיתו particles, הסירו mouse interaction (אין עכבר), בדקו touch10 דקות
6. PerformanceDevTools Performance tab. הוסיפו IntersectionObserver לעצירה. בדקו CPU throttling 4x10 דקות
7. A11yaria-label, role="img", prefers-reduced-motion (עצירה או החלפה בתמונה סטטית)5 דקות
בדקו את עצמכם — מה למדתם בפרק
סיכום הפרק
מה בפרק הבא

בפרק הזה למדתם Canvas 2D — ציור פיקסלי שפותח עולם של particles, generative art, ורקעים דינמיים. Canvas מצייר ב-2D — מה שמספיק להרבה מאוד אפקטים. אבל מה קורה כשאתם רוצים תלת-מימד? מוצר שמסתובב, אובייקט שצף, עולם שלם עם תאורה וצללים? בפרק 11 תלמדו Three.js, Spline ותלת-מימד באינטרנט. תבינו את שלושת עמודי ה-3D (scene, camera, renderer), תכירו את Spline — הכלי הויזואלי שמאפשר לבנות סצנות תלת-ממדיות בלי קוד ולייצא ל-web — ותלמדו מתי 3D שווה את המאמץ ומתי Canvas 2D מספיק. זו השכבה האחרונה בארגז הכלים — ואיתה תוכלו לבנות כל דבר.

→ פרק קודם: Lottie & Rive — אנימציות מוכנות | פרק הבא: Three.js, Spline ותלת-מימד ←