בפרק 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 מנקה את הקנבס, מעדכן מצבים, ומצייר מחדש |
עד עכשיו בקורס, כל הגרפיקה שיצרתם הייתה מבוססת 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 הוא התשובה.
אבל ל-Canvas יש גם חסרונות ברורים. בגלל שהוא לא יוצר אלמנטי DOM: אין accessibility מובנית (קורא מסך לא רואה את הציור), אין event handling על אובייקטים בודדים (צריך לחשב בעצמכם אם העכבר בתוך עיגול), ואי אפשר לעשות inspect ב-DevTools על מה שצייר Canvas. בנוסף, Canvas הוא pixel-based — אם מגדילים אותו, הציור מיטשטש (בניגוד ל-SVG שנשאר חד תמיד). את הבעיה הזו פותרים עם devicePixelRatio — נלמד על זה בהמשך.
הנה הדרך הכי פשוטה לחשוב על זה:
חשוב להבין: Canvas הוא API נמוך. אתם לא אומרים "הנפש את העיגול" — אתם אומרים "מחק הכל, צייר עיגול ב-x+1, חכה 16ms, חזור". זה מרגיש אחרת מ-GSAP או CSS. אבל בדיוק בגלל שהוא נמוך — אתם יכולים לבנות איתו כל דבר. וכ-Vibe Coders, אתם לא צריכים לכתוב הכל מאפס — תלמדו את המושגים, ותגידו ל-AI מה אתם רוצים עם המילים הנכונות.
ב-SVG ו-DOM (retained mode), הדפדפן זוכר מה ציירתם — יש "עץ" של אלמנטים. ב-Canvas (immediate mode), ברגע שציירתם — הדפדפן שכח. אתם צריכים לצייר מחדש הכל בכל frame. אם שכחתם לנקות (clearRect) לפני הציור מחדש — האובייקטים ישאירו שובל. זו הטעות הנפוצה ביותר של מתחילים עם Canvas.
כדי להתחיל לצייר על 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 — נתיב שמגדיר את הצורה לפני הציור.
// עיגול מלא
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 מעלות).
// קו פשוט
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);
<canvas id="c" width="600" height="400" style="background:#111"></canvas>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();ב-Canvas, נקודה (0,0) היא הפינה השמאלית העליונה. ציר X הולך ימינה, ציר Y הולך למטה (לא למעלה כמו במתמטיקה). אז y=100 זה למטה מ-y=50. זה אותו דבר כמו ב-CSS — אבל שווה לזכור כי אנשים שמגיעים מרקע מתמטי מצפים להפך.
עד עכשיו ציירתם תמונה סטטית. כדי להנפיש — צריך לצייר מחדש 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) אתם:
למה requestAnimationFrame ולא setInterval? שלוש סיבות:
במחשב מהיר תקבלו 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);
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();ctx.clearRect ותראו מה קורה — העיגול משאיר שובל!let y = 0; y += 1.5; כדי שהכדור ינוע גם למטה באלכסוןעכשיו שהבנתם את הדפוס הבסיסי, אפשר לבנות כל אנימציה. הטריק הוא שכל אובייקט שאתם רוצים להנפיש — שומרים את המצב שלו (x, y, speed, color, size) באובייקט JavaScript, ובכל frame מעדכנים ומציירים. עם אובייקט אחד — זה כדור שזז. עם 500 אובייקטים — זו מערכת חלקיקים.
מערכת חלקיקים (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.
בנו 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."
בדקו את התוצאה:
עד כה החלקיקים שלנו נצחיים — הם נוצרים פעם אחת וחיים לנצח. אבל הרבה אפקטים דורשים 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 מסירים חלקיקים שמתו — בלי זה, המערך גדל ללא הגבלה והביצועים נפגעים. כלל האצבע: תמיד נקו חלקיקים מתים.
| רכיב | תפקיד | דוגמאות |
|---|---|---|
| Emitter (פולט) | מאיפה נולדים חלקיקים — נקודה, קו, או שטח | מרכז המסך, מיקום העכבר, נקודת קליק, קצה עליון |
| Particle (חלקיק) | אובייקט בודד עם מיקום, מהירות, גודל, צבע, life | עיגול, ריבוע, כוכב, פתית שלג, ניצוץ |
| Forces (כוחות) | מה משפיע על תנועת החלקיק | gravity (למטה), wind (צידה), friction (האטה), attraction (משיכה לנקודה) |
| Lifecycle (מחזור חיים) | נולד עם life=1, מת כש-life=0 | fade out, shrink, color change לפני מוות |
| Connections (חיבורים) | קווים בין חלקיקים קרובים | network lines, constellation effect |
| Interaction (אינטראקציה) | תגובה לעכבר, מגע, או אירועים | repel, attract, spawn on click, follow mouse |
כלל אצבע: כשמתארים מערכת חלקיקים ל-AI, ציינו את כל 6 הרכיבים האלה. ככל שתהיו יותר ספציפיים — התוצאה תהיה יותר מדויקת.
בלי חיבורי קווים: 1,000-5,000 חלקיקים רצים חלק ברוב המכשירים. עם חיבורי קווים: 100-200 חלקיקים, כי הבדיקה היא O(n²) — 200 חלקיקים = 19,900 בדיקות מרחק כל frame. עם חיבורי קווים + mouse interaction: 80-150 חלקיקים. תמיד בדקו על mobile עם CPU throttling 4x ב-DevTools. אם ה-framerate יורד מתחת ל-30fps — הפחיתו חלקיקים.
אם מערכת חלקיקים מאפס מרגישה מורכבת — יש קיצור דרך מושלם. 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
});
// 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);
https://cdn.jsdelivr.net/npm/canvas-confetti@1.9.3/dist/confetti.browser.min.js<button onclick="confetti({particleCount:200, spread:100})">Party!</button>spread ל-360 ו-shapes ל-['star'] ותראו את ההבדלאמנות גנרטיבית (generative art) היא אמנות שנוצרת על ידי אלגוריתם. אתם כותבים כללים — וכל ריצה יוצרת תוצאה שונה. זה לא רק "יפה" — זה שימושי מאוד לאתרים: רקעים ייחודיים שמשתנים בכל טעינה, patterns שנראים אורגניים, טקסטורות שלא חוזרות על עצמן. ו-Canvas הוא הכלי הטבעי ליצירת אמנות גנרטיבית.
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 ומוסיפה:
noise(x, y) — Perlin noise שמחזיר ערכים חלקים בין 0 ל-1setup() ו-draw() — animation loop מובנה (לא צריך requestAnimationFrame)random(min, max) — random נוח יותר מ-Math.random()color(), lerp(), map() — utility functions לאמנותאחד הפטרנים הנפוצים ביותר באמנות גנרטיבית: קווים שזורמים לפי שדה של כיוונים שנקבע על ידי 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.
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); }}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" — זה ההבדל בין תוצאה גנרית לתוצאה מרשימה.
בקשו מ-AI רקע גנרטיבי באחד הסגנונות הבאים. בחרו את מה שמתאים לפרויקט שלכם:
בדקו: האם ה-AI יצר רקע שמשתנה בכל טעינה? האם הוא עדין מספיק שלא מפריע לתוכן? האם הוא רספונסיבי?
אחד השימושים הנפוצים ביותר של Canvas באתרים מקצועיים הוא רקעים דינמיים — גרדיאנטים שזוחלים, אורורה שנושמת, mesh gradients שמשתנים. אלה אפקטים שאפשר לעשות חלקית עם CSS (background animation) — אבל Canvas נותן שליטה מלאה וביצועים טובים יותר.
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), הצבעים "נושמים" — משתנים בצורה חלקה ומעגלית, בלי לחזור על אותו דפוס.
אפקט אורורה (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 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.
position: fixed; top: 0; left: 0; z-index: -1background: rgba(0,0,0,0.5)זוכרים את ScrollTrigger מפרק 5? עכשיו שאתם מכירים Canvas, יש טכניקה חדשה שמשלבת את שניהם: scroll-driven Canvas. במקום לחבר את הגלילה לאנימציות CSS או GSAP, אתם מחברים אותה ישירות לציור ב-Canvas. התוצאה: אפקטים שלא ניתנים להשגה בשום דרך אחרת.
הטכניקה הידועה ביותר: 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>.
148 תמונות × 50KB כל אחת = 7.4MB שצריך להוריד. זה כבד. פתרונות: (1) דחיסה אגרסיבית — WebP או AVIF יכולים להוריד ל-20KB לתמונה. (2) Progressive loading — מתחילים עם 30 frames ברזולוציה נמוכה, טוענים את הנותרים ברקע. (3) ב-mobile אפשר להסתפק ב-60 frames במקום 148. (4) שקלו אם video element עם מניפולציית currentTime לא עדיף — פחות bandwidth, אותה תוצאה.
אפשרות פשוטה יותר: במקום סדרת תמונות, מציירים ב-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 מצייר. הנה פרומפט שיעזור לכם:
בחרו אחד מהשילובים הבאים ובקשו מ-AI:
בדקו: הגלילה חלקה? האנימציה לא "קופצת" בין מצבים? האם היא עובדת גם למעלה וגם למטה (bidirectional)?
Canvas מהיר — אבל לא אינסופי. אם לא שמים לב לביצועים, Canvas יכול להפוך מאפקט מרשים לבעיית performance שהורסת את חווית המשתמש. הנה כל מה שצריך לדעת.
הבעיה הנפוצה ביותר: 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 הוא 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.
| כלל | למה | איך |
|---|---|---|
| devicePixelRatio | ציור חד ב-Retina | canvas.width = rect.width * dpr; ctx.scale(dpr, dpr) |
| Batch draw calls | כל beginPath → fill הוא קריאה יקרה | צרו path אחד עם כל הנקודות, fill פעם אחת |
| Offscreen canvas | Pre-render אובייקטים מורכבים | ציירו פעם אחת → drawImage בכל frame |
| Limit particle count | כל חלקיק = חישוב + ציור | Desktop: 200-500 (עם קווים), 2000+ (בלי). Mobile: חצי |
| Clean dead particles | מערך שגדל = memory leak | filter() כל frame, או object pool |
| Pause when invisible | לא שורפים CPU ברקע | Intersection Observer → cancelAnimationFrame |
| Throttle mouse events | mousemove fires מאות פעמים בשנייה | שמרו מיקום, קראו רק ב-animation loop |
| Avoid ctx.filter | blur() על canvas כבד מאוד | השתמשו ב-CSS filter על ה-canvas element במקום |
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 שם חיי סוללה קריטיים.
אחרי שלמדתם CSS animations (פרק 2), SVG (פרק 8), ועכשיו Canvas — הגיע הזמן לתמונה המלאה. מתי כל טכנולוגיה? הנה Framework ההחלטה:
| קריטריון | CSS | SVG | Canvas |
|---|---|---|---|
| כמות אובייקטים | 1-20 | 10-300 | 100-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 מוכנה? גם אפשר — יותר קל אבל פחות גמיש. השאלה היא תמיד: כמה שליטה צריך וכמה אובייקטים.
כ-Vibe Coders, הכוח שלכם הוא לדעת מה לבקש. עכשיו שאתם מכירים את המושגים — particles, animation loop, Perlin noise, offscreen canvas, devicePixelRatio — הפרומפטים שלכם ל-AI יהיו מדויקים בהרבה. הנה 5 פרומפטים מוכנים לשימוש:
"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."
"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."
"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."
"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."
"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."
עכשיו שיש לכם את הבסיס, בואו נראה 4 שימושים מקצועיים של Canvas באתרים אמיתיים — תבניות שתוכלו לבקש מ-AI ולשלב בפרויקטים.
ה-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".
כש-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.
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.
אפקט 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.
בחרו שניים מבין הרכיבים הבאים ושלבו בפרויקט:
השתמשו ב-AI: קחו את הפרומפטים מסעיף 10.11 כבסיס, התאימו לפרויקט שלכם (צבעים, גדלים, particle count). ודאו שכל רכיב:
| שלב | פעולה | זמן |
|---|---|---|
| 1. החלטה | מה האפקט? Particles, confetti, gradient, generative? האם Canvas הכלי הנכון — או SVG/CSS מספיקים? | 5 דקות |
| 2. פרומפט | כתבו פרומפט מדויק ל-AI עם כל הפרטים: particle count, colors, behavior, interactions, device support | 5 דקות |
| 3. בניה | שלבו את הקוד שה-AI ייצר. בדקו שהוא כולל: dpr, resize, clearRect בכל frame | 15 דקות |
| 4. התאמה | שנו צבעים ל-brand, particle count, speed, opacity. בדקו שזה עדין — רקע, לא מרכז תשומת לב | 10 דקות |
| 5. Responsive | בדקו mobile: הפחיתו particles, הסירו mouse interaction (אין עכבר), בדקו touch | 10 דקות |
| 6. Performance | DevTools Performance tab. הוסיפו IntersectionObserver לעצירה. בדקו CPU throttling 4x | 10 דקות |
| 7. A11y | aria-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 מספיק. זו השכבה האחרונה בארגז הכלים — ואיתה תוכלו לבנות כל דבר.