Skip to content

Commit

Permalink
Added more default particle shapes + custom image loader
Browse files Browse the repository at this point in the history
  • Loading branch information
Aneks1 committed Feb 28, 2025
1 parent e0e5313 commit 48b81a9
Show file tree
Hide file tree
Showing 43 changed files with 750 additions and 250 deletions.
35 changes: 1 addition & 34 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,37 +15,4 @@ $ npm i css-particles
<script src="https://aneks1.github.io/particulab/particulab.umd/index.js"></script>
```

## Usage

### Create the Particle System
```ts
import particulab from 'particulab'
const canvas = document.getElementById('canvas') as HTMLCanvasElement
const system = new particulab.ParticleSystem(canvas, { x: canvas.width, y: canvas.height })
```

### Set the amount of particles to show
```ts
system.amount = 100
```

### Set the properties for the particles
```ts
system.size = particulab.range(1, 3)
system.life = particulab.range(5, 10)
system.speed = { x: particulab.range(-2, 2), y: particulab.range(-2, 2) }
system.colors.push(new particulab.HEX("ffffff"))
system.colors.push(new particulab.RGBA(255, 255, 0, 1))
system.opacity = particulab.range(50, 100)
```

### You can set fade types!
```ts
system.fadeIn = { duration: 1, opacity: 0, scaleFactor: 0.5 }
system.fadeOut = { duration: 2, opacity: 0, scaleFactor: 2 }
```

### Init the particle system
```ts
system.init()
```
## [Go to Docs](docs/main.md)
3 changes: 0 additions & 3 deletions TODO.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
# TODO

- SVG loading
- More default shapes

- Add custom particle prefab to system

- Cursor Gravity
Expand Down
180 changes: 129 additions & 51 deletions dist/particulab.es.js
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
var u = Object.defineProperty;
var I = (s, t, i) => t in s ? u(s, t, { enumerable: !0, configurable: !0, writable: !0, value: i }) : s[t] = i;
var e = (s, t, i) => I(s, typeof t != "symbol" ? t + "" : t, i);
class m {
constructor(t, i, a, o) {
var g = Object.defineProperty;
var I = (n, t, i) => t in n ? g(n, t, { enumerable: !0, configurable: !0, writable: !0, value: i }) : n[t] = i;
var e = (n, t, i) => I(n, typeof t != "symbol" ? t + "" : t, i);
class z {
constructor(t, i, a, s) {
e(this, "red");
e(this, "green");
e(this, "blue");
e(this, "alpha");
if (this.red = t, this.green = i, this.blue = a, this.alpha = o, t > 255 || t < 0 || i > 255 || i < 0 || a > 255 || a < 0 || o > 1 || o < 0) throw new TypeError("Invalid rgba color.");
if (this.red = t, this.green = i, this.blue = a, this.alpha = s, t > 255 || t < 0 || i > 255 || i < 0 || a > 255 || a < 0 || s > 1 || s < 0) throw new TypeError("Invalid rgba color.");
}
toHex() {
const t = this._componentToHex(this.red), i = this._componentToHex(this.green), a = this._componentToHex(this.blue);
return new r(`#${t}${i}${a}`);
return new l(`#${t}${i}${a}`);
}
_componentToHex(t) {
const i = t.toString(16);
Expand All @@ -21,20 +21,20 @@ class m {
return `rgba(${this.red}, ${this.green}, ${this.blue}, ${this.alpha})`;
}
}
class r {
class l {
constructor(t) {
e(this, "hex");
t[0] === "#" && (t = t.slice(1)), t.length === 3 && (t = t.split("").map((i) => i + i).join("")), this.hex = t;
}
toRGB() {
const t = parseInt(this.hex.slice(0, 2), 16), i = parseInt(this.hex.slice(2, 4), 16), a = parseInt(this.hex.slice(4, 6), 16);
return new m(t, i, a, 1);
return new z(t, i, a, 1);
}
toString() {
return `#${this.hex}`;
}
}
class p {
class f {
constructor(t, i) {
e(this, "parent");
e(this, "options");
Expand All @@ -44,15 +44,15 @@ class p {
this.parent = t, this.options = i;
}
}
class y extends p {
class y extends f {
constructor(t, i) {
super(t, i), this.calculateDeltas();
}
calculateDeltas() {
this.options.opacity != null && (this.deltaOpacity = (this.options.opacity - this.parent.opacity) / this.options.duration), this.options.scaleFactor != null && (this.deltaSize = (this.options.scaleFactor * this.parent.size - this.parent.size) / this.options.duration);
}
}
class g extends p {
class w extends f {
constructor(i, a) {
super(i, a);
e(this, "initialLife");
Expand All @@ -63,15 +63,44 @@ class g extends p {
}
}
class x {
constructor(t, i) {
drawCircle(t, i) {
t.beginPath(), t.arc(i.position.x, i.position.y, i.size / 2, 0, 2 * Math.PI, !1), t.closePath(), t.fill();
}
drawRectangle(t, i) {
t.fillRect(i.position.x - i.size / 2, i.position.y - i.size / 2, i.size, i.size);
}
drawTriangle(t, i) {
const a = i.size * Math.sqrt(3) / 2;
t.beginPath(), t.moveTo(i.position.x, i.position.y - a / 2), t.lineTo(i.position.x - i.size / 2, i.position.y + a / 2), t.lineTo(i.position.x + i.size / 2, i.position.y + a / 2), t.closePath(), t.fill();
}
drawStar(t, i) {
const s = i.size / 2, d = s / 2.5;
let c = Math.PI / 5;
t.beginPath();
for (let r = 0; r < 5 * 2; r++) {
let p = r % 2 === 0 ? s : d, u = i.position.x + Math.cos(c * r) * p, m = i.position.y + Math.sin(c * r) * p;
t.lineTo(u, m);
}
t.closePath(), t.fill();
}
drawImage(t, i, a) {
i.drawImage(t.element, a.position.x - a.size / 2, a.position.y - a.size / 2, a.size, a.size);
}
}
class M {
constructor(t, i, a) {
e(this, "parent");
e(this, "id");
e(this, "animationFramId");
e(this, "lastUpdate", performance.now());
e(this, "shapeManager", new x());
e(this, "position", { x: 0, y: 0 });
e(this, "size", 0);
e(this, "life", 0);
e(this, "speed", { x: 0, y: 0 });
e(this, "color", new r("#ffffff"));
e(this, "color", new l("#ffffff"));
e(this, "opacity", 100);
e(this, "shape", "circle");
// Fade Properties
e(this, "fadeOut");
e(this, "fadeIn");
Expand All @@ -80,65 +109,114 @@ class x {
this.parent = i, this.id = t;
}
init() {
var i, a, o, d;
this.fadeOut && (this.fadeOut.opacity != null || this.fadeOut.scaleFactor != null) && (this.fadeOutHandler = new y(this, this.fadeOut)), this.fadeIn && (this.fadeIn.opacity != null || this.fadeIn.scaleFactor != null) && (this.fadeInHandler = new g(this, this.fadeIn)), this.opacity = ((i = this.fadeIn) == null ? void 0 : i.opacity) != null ? (a = this.fadeIn) == null ? void 0 : a.opacity : Math.max(0, Math.min(100, this.opacity)), this.size = ((o = this.fadeIn) == null ? void 0 : o.scaleFactor) != null ? (d = this.fadeIn) == null ? void 0 : d.scaleFactor : Math.max(0, this.size), this.life = Math.max(0, this.life);
const t = setInterval(() => {
var l, c, f;
this.position.x += this.speed.x * 60 / 1e3, this.position.y -= this.speed.y * 60 / 1e3, this.fadeIn && this.fadeInHandler && this.life >= this.fadeInHandler.initialLife - this.fadeIn.duration && (this.opacity += ((l = this.fadeInHandler) == null ? void 0 : l.deltaOpacity) * (1 / 60), this.opacity = Math.max(0, Math.min(100, this.opacity)), this.size += this.fadeInHandler.deltaSize * (1 / 60), this.size = Math.max(0, this.size)), this.fadeOut && this.fadeOutHandler && this.life <= ((c = this.fadeOut) == null ? void 0 : c.duration) && (this.opacity += ((f = this.fadeOutHandler) == null ? void 0 : f.deltaOpacity) * (1 / 60), this.opacity = Math.max(0, Math.min(100, this.opacity)), this.size += this.fadeOutHandler.deltaSize * (1 / 60), this.size = Math.max(0, this.size)), this.life -= 1 / 60, this.life <= 0 && (clearInterval(t), this.parent.particles.delete(this.id));
}, this.parent.deltaTime);
var t, i, a, s;
this.fadeOut && (this.fadeOut.opacity != null || this.fadeOut.scaleFactor != null) && (this.fadeOutHandler = new y(this, this.fadeOut)), this.fadeIn && (this.fadeIn.opacity != null || this.fadeIn.scaleFactor != null) && (this.fadeInHandler = new w(this, this.fadeIn)), this.opacity = ((t = this.fadeIn) == null ? void 0 : t.opacity) != null ? (i = this.fadeIn) == null ? void 0 : i.opacity : Math.max(0, Math.min(100, this.opacity)), this.size = ((a = this.fadeIn) == null ? void 0 : a.scaleFactor) != null ? (s = this.fadeIn) == null ? void 0 : s.scaleFactor : Math.max(0, this.size), this.life = Math.max(0, this.life), this.update();
}
update() {
var a, s, d;
const t = performance.now(), i = (t - this.lastUpdate) / 1e3;
this.lastUpdate = t, this.position.x += this.speed.x * i, this.position.y -= this.speed.y * i, this.fadeIn && this.fadeInHandler && this.life >= this.fadeInHandler.initialLife - this.fadeIn.duration && (this.opacity += ((a = this.fadeInHandler) == null ? void 0 : a.deltaOpacity) * i, this.opacity = Math.max(0, Math.min(100, this.opacity)), this.size += this.fadeInHandler.deltaSize * i, this.size = Math.max(0, this.size)), this.fadeOut && this.fadeOutHandler && this.life <= ((s = this.fadeOut) == null ? void 0 : s.duration) && (this.opacity += ((d = this.fadeOutHandler) == null ? void 0 : d.deltaOpacity) * i, this.opacity = Math.max(0, Math.min(100, this.opacity)), this.size += this.fadeOutHandler.deltaSize * i, this.size = Math.max(0, this.size)), this.life -= 1 / 60, this.life <= 0 && this.delete(), this.animationFramId = requestAnimationFrame(this.update.bind(this));
}
delete() {
cancelAnimationFrame(this.animationFramId), this.parent.particles.delete(this.id);
}
draw(t) {
switch (t.fillStyle = this.color.toString(), t.globalAlpha = this.opacity / 100, this.shape) {
case "circle":
this.shapeManager.drawCircle(t, { position: this.position, size: this.size });
case "rectangle":
this.shapeManager.drawRectangle(t, { position: this.position, size: this.size });
case "triangle":
this.shapeManager.drawTriangle(t, { position: this.position, size: this.size });
case "star":
this.shapeManager.drawStar(t, { position: this.position, size: this.size });
default:
this.shape.element ? this.shapeManager.drawImage(this.shape, t, { position: this.position, size: this.size }) : console.warn("Invalid shape " + this.shape);
}
}
}
function h(s, t) {
return { min: s, max: t };
function o(n, t) {
return { min: n, max: t };
}
class n {
class h {
constructor(t, i) {
e(this, "canvas");
e(this, "canvasSize");
e(this, "lastId", 0);
e(this, "deltaTime", 1e3 / 60);
e(this, "amount", 0);
e(this, "particles", /* @__PURE__ */ new Map());
e(this, "size", h(1, 5));
e(this, "life", h(10, 15));
e(this, "speed", { x: h(-10, 10), y: h(-10, 10) });
e(this, "colors", []);
e(this, "opacity", h(50, 100));
e(this, "_ctx");
e(this, "animationFramId");
e(this, "amount");
e(this, "life");
e(this, "size");
e(this, "speed");
e(this, "colors");
e(this, "opacity");
e(this, "fadeOut");
e(this, "fadeIn");
this.canvas = t, this.canvasSize = i, t.width = i.x, t.height = i.y;
e(this, "shapes");
this.canvas = t, this.canvasSize = i.canvasSize, t.width = i.canvasSize.x, t.height = i.canvasSize.y, this._ctx = t.getContext("2d"), this.amount = i.amount || 0, this.life = i.life || o(10, 15), this.size = i.size || o(1, 5), this.speed = i.speed || { x: o(-10, 10), y: o(-10, 10) }, this.colors = i.colors || [], this.opacity = i.opacity || o(50, 100), this.fadeOut = i.fadeOut, this.fadeIn = i.fadeIn, this.shapes = i.shapes || [];
}
static getRandomNumberInInterval(t) {
static numberInRange(t) {
const i = Math.ceil(t.min), a = Math.floor(t.max);
return Math.floor(Math.random() * (a - i + 1)) + i;
}
static getRandomElementFromArray(t) {
static elementFromArray(t) {
const i = Math.floor(Math.random() * t.length);
return t[i];
}
createParticle() {
const t = new x(this.lastId.toString(), this);
t.position.x = n.getRandomNumberInInterval({ min: 0, max: this.canvasSize.x }), t.position.y = n.getRandomNumberInInterval({ min: 0, max: this.canvasSize.y }), t.size = n.getRandomNumberInInterval(this.size), t.life = n.getRandomNumberInInterval(this.life), t.speed.x = n.getRandomNumberInInterval(this.speed.x), t.speed.y = n.getRandomNumberInInterval(this.speed.y), t.color = n.getRandomElementFromArray(this.colors || new r("fff")), t.opacity = n.getRandomNumberInInterval(this.opacity), t.fadeOut = this.fadeOut, t.fadeIn = this.fadeIn, t.init(), this.particles.set(this.lastId.toString(), t), this.lastId++;
const t = new M(this.lastId.toString(), this);
t.position.x = h.numberInRange({ min: 0, max: this.canvasSize.x }), t.position.y = h.numberInRange({ min: 0, max: this.canvasSize.y }), t.size = h.numberInRange(this.size), t.life = h.numberInRange(this.life), t.speed.x = h.numberInRange(this.speed.x), t.speed.y = h.numberInRange(this.speed.y), t.color = h.elementFromArray(this.colors) || new l("fff"), t.opacity = h.numberInRange(this.opacity), t.fadeOut = this.fadeOut, t.fadeIn = this.fadeIn, t.shape = h.elementFromArray(this.shapes) || "circle", t.init(), this.particles.set(this.lastId.toString(), t), this.lastId++;
}
init() {
const t = this.canvas.getContext("2d");
for (let i = 0; i < this.amount; i++) this.createParticle();
setInterval(() => {
if (this.particles.size < this.amount)
for (let i = this.particles.size; i < this.amount; i++) this.createParticle();
t == null || t.clearRect(0, 0, this.canvas.width, this.canvas.height), this.particles.forEach((i) => {
t.fillStyle = i.color.toString(), t.globalAlpha = i.opacity / 100, t == null || t.beginPath(), t == null || t.arc(i.position.x, i.position.y, i.size / 2, 0, 2 * Math.PI, !1), t == null || t.closePath(), t == null || t.fill();
});
}, this.deltaTime);
for (let t = 0; t < this.amount; t++) this.createParticle();
this.update();
}
update() {
var t;
if (this.particles.size < this.amount) for (let i = this.particles.size; i < this.amount; i++) this.createParticle();
(t = this._ctx) == null || t.clearRect(0, 0, this.canvas.width, this.canvas.height);
for (const [i, a] of this.particles) a.draw(this._ctx);
this.animationFramId = requestAnimationFrame(this.update.bind(this));
}
stop() {
cancelAnimationFrame(this.animationFramId);
}
clear() {
var t;
(t = this._ctx) == null || t.clearRect(0, 0, this.canvas.width, this.canvas.height), this.particles = /* @__PURE__ */ new Map();
}
}
class H {
constructor(t) {
e(this, "src");
e(this, "element");
this.src = t, this.loadImage();
}
async loadImage() {
try {
this.element = await this.createImage(this.src);
} catch (t) {
console.error(`Failed to load image: ${this.src}`, t);
}
}
createImage(t) {
return new Promise((i, a) => {
const s = new Image();
s.src = t, s.onload = () => i(s), s.onerror = () => a(new Error(`Image failed to load: ${t}`));
});
}
}
export {
p as FadeHandler,
g as FadeInHandler,
f as FadeHandler,
w as FadeInHandler,
y as FadeOutHandler,
r as HEX,
x as Particle,
n as ParticleSystem,
m as RGBA,
h as range
l as HEX,
M as Particle,
H as ParticleImage,
h as ParticleSystem,
z as RGBA,
x as ShapeManager,
o as range
};
Loading

0 comments on commit 48b81a9

Please sign in to comment.