הוספתם אנימציית חלקיקי רקע לאתר שלכם, אבל קיבלתם פיגור במובייל וקונפליקט עם Core Web Vitals. נשמע מוכר? נתקלנו בזה עשרות פעמים ופיתחנו גישה שמספקת אפקט יפה ללא אובדן ביצועים. הצוות שלנו משתמש רק ב-tsParticles—היורש של Particles.js הנטוש, עם טיפוסי TypeScript וארכיטקטורה מודולרית. אנחנו מבטיחים שהאנימציה לא תפגע ב-LCP וב-INP, ותושבת במכשירים חלשים אם צריך. הניסיון מראה שתצורה נכונה מפחיתה את זמן הטעינה במובייל ב-40% בהשוואה ל-Particles.js. אפשר גם ליישם שלג, קונפטי או שמיים זרועי כוכבים—כל preset מותאם למותג שלכם.
איך לבחור בין Particles.js ל-tsParticles?
Particles.js לא עודכן כבר שנים—הוא לא תומך בפריימוורקים מודרניים ולא מותאם ל-SSR. tsParticles הוא fork פעיל עם 10k כוכבים ב-GitHub, תמיכה מובנית ב-React, Vue ו-Angular, ויכולת לטעון רק את ה-presets הדרושים. tsParticles מהיר פי 3 ברינדור במובייל בזכות המודולריות. טבלת השוואה:
| קריטריון | Particles.js | tsParticles |
|---|---|---|
| תמיכה | נטוש מאז 2016 | פעיל (שחרורים חודשיים) |
| TypeScript | לא | טיפוסים מלאים |
| מודולריות | מונולית ~150 KB | Presets מ-40 KB gzip |
| אינטראקטיביות | רק hover | Hover, click, grab, bubble, repulse |
| SSR/Next.js | לא נתמך | @tsparticles/react + ייבוא דינמי |
אם זה פרויקט חדש—השתמשו ב-tsParticles. אנחנו מעבירים את כל הלקוחות מ-Particles.js ל-tsParticles, וחוסכים בממוצע 30% מזמן הדיבוג.
איך לשלב tsParticles ב-React?
שלב 1. התקינו את הבנייה המודולרית. הגדרה מינימלית: core + אלמנטים בסיסיים:
npm install @tsparticles/react @tsparticles/engine @tsparticles/slim שלב 2. צרו קומפוננטה עם טעינה דינמית של המנוע:
// components/ParticlesBackground.tsx
'use client'
import { useEffect, useState, useCallback } from 'react'
import Particles, { initParticlesEngine } from '@tsparticles/react'
import { loadSlim } from '@tsparticles/slim'
import type { ISourceOptions } from '@tsparticles/engine'
const particleOptions: ISourceOptions = {
background: {
color: {
value: 'transparent',
},
},
fpsLimit: 60,
interactivity: {
events: {
onHover: {
enable: true,
mode: 'repulse',
},
onClick: {
enable: true,
mode: 'push',
},
},
modes: {
repulse: {
distance: 100,
duration: 0.4,
},
push: {
quantity: 4,
},
},
},
particles: {
color: {
value: '#3b82f6',
},
links: {
color: '#3b82f6',
distance: 150,
enable: true,
opacity: 0.3,
width: 1,
},
move: {
enable: true,
speed: 1.5,
direction: 'none',
random: false,
straight: false,
outModes: {
default: 'bounce',
},
},
number: {
value: 60,
density: {
enable: true,
area: 800,
},
},
opacity: {
value: 0.5,
},
shape: {
type: 'circle',
},
size: {
value: {
min: 1,
max: 3,
},
},
},
detectRetina: true,
}
export function ParticlesBackground() {
const [engineReady, setEngineReady] = useState(false)
useEffect(() => {
initParticlesEngine(async (engine) => {
await loadSlim(engine)
}).then(() => setEngineReady(true))
}, [])
if (!engineReady) return null
return (
<Particles
id="tsparticles"
options={particleOptions}
className="absolute inset-0 -z-10"
/>
)
} למה ביצועים במובייל חשובים
למכשירים ניידים וחלשים, אנחנו מוסיפים לוגיקה אדפטיבית: בדיקת npm install @tsparticles/react @tsparticles/engine @tsparticles/slim , // components/ParticlesBackground.tsx 'use client' import { useEffect, useState, useCallback } from 'react' import Particles, { initParticlesEngine } from '@tsparticles/react' import { loadSlim } from '@tsparticles/slim' import type { ISourceOptions } from '@tsparticles/engine' const particleOptions: ISourceOptions = { background: { color: { value: 'transparent' }, }, fpsLimit: 60, interactivity: { events: { onHover: { enable: true, mode: 'repulse', }, onClick: { enable: true, mode: 'push', }, }, modes: { repulse: { distance: 100, duration: 0.4 }, push: { quantity: 4 }, }, }, particles: { color: { value: '#3b82f6' }, links: { color: '#3b82f6', distance: 150, enable: true, opacity: 0.3, width: 1, }, move: { enable: true, speed: 1.5, direction: 'none', random: false, straight: false, outModes: { default: 'bounce' }, }, number: { value: 60, density: { enable: true, area: 800 }, }, opacity: { value: 0.5 }, shape: { type: 'circle' }, size: { value: { min: 1, max: 3 } }, }, detectRetina: true, } export function ParticlesBackground() { const [engineReady, setEngineReady] = useState(false) useEffect(() => { initParticlesEngine(async (engine) => { await loadSlim(engine) }).then(() => setEngineReady(true)) }, []) if (!engineReady) return null return ( <Particles id="tsparticles" options={particleOptions} className="absolute inset-0 -z-10" /> ) } ו-window.innerWidth. אם המכשיר חלש, הקומפוננטה לא מתרנדרת. זה לא מגדיל את LCP ולא גורם ל-jank. בפרויקטים שלנו, בדיקה כזו מפחיתה את זמן הטעינה במובייל בממוצע ב-200 ms.
Preset: Networking (רשת מחוברת)
// presets/network.ts
import type { ISourceOptions } from '@tsparticles/engine'
export const networkPreset: ISourceOptions = {
fpsLimit: 60,
particles: {
number: {
value: 80,
density: {
enable: true,
area: 1000
}
},
color: {
value: ['#3b82f6', '#8b5cf6', '#06b6d4']
},
shape: {
type: 'circle'
},
opacity: {
value: {
min: 0.3,
max: 0.8
},
animation: {
enable: true,
speed: 1,
minimumValue: 0.1
}
},
size: {
value: {
min: 1,
max: 4
},
animation: {
enable: true,
speed: 2,
minimumValue: 0.5
}
},
links: {
enable: true,
distance: 120,
color: {
value: '#94a3b8'
},
opacity: 0.2,
width: 1,
triangles: {
enable: false
}
},
move: {
enable: true,
speed: {
min: 0.5,
max: 1.5
},
direction: 'none',
random: true,
straight: false,
outModes: {
default: 'out'
}
}
},
interactivity: {
events: {
onHover: {
enable: true,
mode: ['grab', 'bubble']
},
onClick: {
enable: true,
mode: 'repulse'
},
resize: {
enable: true
}
},
modes: {
grab: {
distance: 140,
links: {
opacity: 0.8
}
},
bubble: {
distance: 100,
size: 8,
duration: 0.3,
opacity: 0.8
},
repulse: {
distance: 150,
duration: 0.4
}
}
},
detectRetina: true
} Preset: שלג
לאפקט שלג יורד, השתמשו ב-preset הבא (עוצמה וגודל פתיתים ניתנים להתאמה):
// presets/snow.ts
import type { ISourceOptions } from '@tsparticles/engine'
export const snowPreset: ISourceOptions = {
fpsLimit: 60,
particles: {
number: {
value: 200,
density: {
enable: true,
area: 800
}
},
color: {
value: '#ffffff'
},
shape: {
type: 'circle'
},
opacity: {
value: {
min: 0.4,
max: 0.9
}
},
size: {
value: {
min: 1,
max: 6
}
},
move: {
enable: true,
speed: {
min: 0.5,
max: 2
},
direction: 'bottom',
random: true,
straight: false,
outModes: {
default: 'out'
},
},
wobble: {
enable: true,
distance: 10,
speed: 5
},
},
detectRetina: true,
} קונפטי באירוע
// hooks/useConfetti.ts
import { useCallback } from 'react'
import { tsParticles } from '@tsparticles/engine'
export function useConfetti() {
const fire = useCallback(async (originX = 0.5, originY = 0.6) => {
await tsParticles.load({
id: 'confetti-' + Date.now(),
options: {
fullScreen: {
enable: true,
zIndex: 100
},
fpsLimit: 60,
particles: {
number: {
value: 0
},
color: {
value: ['#f59e0b', '#3b82f6', '#10b981', '#ef4444', '#8b5cf6']
},
shape: {
type: ['square', 'circle']
},
opacity: {
value: 1,
animation: {
enable: true,
speed: 0.5,
startValue: 'max',
destroy: 'min'
}
},
size: {
value: { min: 4, max: 10 }
},
rotate: {
value: { min: 0, max: 360 },
animation: {
enable: true,
speed: 20,
sync: false
}
},
tilt: {
value: { min: 0, max: 360 },
enable: true,
animation: {
enable: true,
speed: 15,
sync: false
}
},
move: {
enable: true,
speed: { min: 8, max: 15 },
direction: 'bottom',
gravity: {
enable: true,
acceleration: 9.8
},
drift: { min: -2, max: 2 },
decay: { min: 0.02, max: 0.04 },
outModes: {
default: 'destroy',
top: 'none'
}
}
},
emitters: {
direction: 'top',
life: {
count: 1,
duration: 0.1,
delay: 0
},
rate: {
delay: 0,
quantity: 150
},
size: {
width: 0,
height: 0
},
position: {
x: originX * 100,
y: originY * 100
}
}
}
})
}, [])
return { fire }
} // Использование const { fire } = useConfetti() const handleFormSubmit = async () => { await submitForm() fire() } השוואת גודלי Preset
| Preset | גודל (gzip) | תכונות |
|---|---|---|
prefers-reduced-motion | ~40 KB | צורות בסיסיות, תנועה, אינטראקציה |
navigator.hardwareConcurrency | ~180 KB | כל ה-presets (שלג, קונפטי, כוכבים וכו') |
// presets/network.ts import type { ISourceOptions } from '@tsparticles/engine' export const networkPreset: ISourceOptions = { fpsLimit: 60, particles: { number: { value: 80, density: { enable: true, area: 1000 } }, color: { value: ['#3b82f6', '#8b5cf6', '#06b6d4'] }, shape: { type: 'circle' }, opacity: { value: { min: 0.3, max: 0.8 }, animation: { enable: true, speed: 1, minimumValue: 0.1 }, }, size: { value: { min: 1, max: 4 }, animation: { enable: true, speed: 2, minimumValue: 0.5 }, }, links: { enable: true, distance: 120, color: { value: '#94a3b8' }, opacity: 0.2, width: 1, triangles: { enable: false, }, }, move: { enable: true, speed: { min: 0.5, max: 1.5 }, direction: 'none', random: true, straight: false, outModes: { default: 'out' }, }, }, interactivity: { events: { onHover: { enable: true, mode: ['grab', 'bubble'] }, onClick: { enable: true, mode: 'repulse' }, resize: { enable: true }, }, modes: { grab: { distance: 140, links: { opacity: 0.8 } }, bubble: { distance: 100, size: 8, duration: 0.3, opacity: 0.8 }, repulse: { distance: 150, duration: 0.4 }, }, }, detectRetina: true, } (core בלבד) | ~20 KB | רק core, ללא אלמנטים |
טעויות נפוצות ב-tsParticles
- טעינת חבילת
// presets/snow.ts import type { ISourceOptions } from '@tsparticles/engine' export const snowPreset: ISourceOptions = { fpsLimit: 60, particles: { number: { value: 200, density: { enable: true, area: 800 } }, color: { value: '#ffffff' }, shape: { type: 'circle' }, opacity: { value: { min: 0.4, max: 0.9 } }, size: { value: { min: 1, max: 6 } }, move: { enable: true, speed: { min: 0.5, max: 2 }, direction: 'bottom', random: true, straight: false, outModes: { default: 'out' }, }, wobble: { enable: true, distance: 10, speed: 5 }, }, detectRetina: true, }המלאה שלא לצורך—מוסיפה 140 KB ל-bundle. - התעלמות מ-
// hooks/useConfetti.ts import { useCallback } from 'react' import { tsParticles } from '@tsparticles/engine' export function useConfetti() { const fire = useCallback(async (originX = 0.5, originY = 0.6) => { await tsParticles.load({ id: 'confetti-' + Date.now(), options: { fullScreen: { enable: true, zIndex: 100 }, fpsLimit: 60, particles: { number: { value: 0 }, color: { value: ['#f59e0b', '#3b82f6', '#10b981', '#ef4444', '#8b5cf6'], }, shape: { type: ['square', 'circle'] }, opacity: { value: 1, animation: { enable: true, speed: 0.5, startValue: 'max', destroy: 'min', }, }, size: { value: { min: 4, max: 10 } }, rotate: { value: { min: 0, max: 360 }, animation: { enable: true, speed: 20, sync: false }, }, tilt: { value: { min: 0, max: 360 }, enable: true, animation: { enable: true, speed: 15, sync: false }, }, move: { enable: true, speed: { min: 8, max: 15 }, direction: 'bottom', gravity: { enable: true, acceleration: 9.8 }, drift: { min: -2, max: 2 }, decay: { min: 0.02, max: 0.04 }, outModes: { default: 'destroy', top: 'none' }, }, }, emitters: { direction: 'top', life: { count: 1, duration: 0.1, delay: 0 }, rate: { delay: 0, quantity: 150 }, size: { width: 0, height: 0 }, position: { x: originX * 100, y: originY * 100 }, }, }, }) }, []) return { fire } }—חלקיקים נראים מטושטשים במסכי Retina. - שימוש ב-
// Использование const { fire } = useConfetti() const handleFormSubmit = async () => { await submitForm() fire() }כדי לחסוך בסוללה—עדיף לשמור על 60 ולכבות דינמית את האנימציה ב-@tsparticles/slim. - חוסר כיבוי אדפטיבי במובייל—גורם לפיגור במכשירים חלשים.
למה להזמין מאיתנו יישום?
אנחנו צוות עם ניסיון של 5+ שנים באנימציות ופיתוח web. יישמנו מעל 50 פרויקטי חלקיקים: מרקעים פשוטים ועד סצנות אינטראקטיביות מורכבות. המהנדסים שלנו יודעים להימנע ממלכודות נפוצות: בקשות N+1 בטעינה, שימוש שגוי ב-@tsparticles/all, דליפות זיכרון במעברי route. אנחנו מספקים אחריות ביצועים: LCP לא יעלה ביותר מ-50 ms לאחר שילוב האפקט. צרו קשר לייעוץ והערכת פרויקט.
מה כלול בעבודה
- ניתוח: הערכת ביצועים נוכחיים, בחירת preset מתאים.
- עיצוב: הגדרת חלקיקים למותג, התאמת רספונסיביות.
- יישום: שילוב עם React/Next.js, כתיבת presets מותאמים.
- בדיקות: אימות במובייל ובמכשירים חלשים, מדידת Core Web Vitals.
- פריסה: התקנה לפרודקשן, מסירת תיעוד התאמה אישית.
לאחר סיום תקבלו: קוד מקור עם הערות, גישה ל-repository, שעת הדרכה למפתח שלכם, ותמיכה עם אחריות לשבועיים.
לוח זמנים טיפוסי
רקע חלקיקים בסיסי—3-4 שעות. מספר presets עם הגדרות מותאמות, קונפטי, כיבוי אדפטיבי ואופטימיזציית ביצועים—1-2 ימי עסקים. המחיר מחושב באופן אישי. קבלו ייעוץ—נחזור תוך שעה.







