particle-network-bg 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +198 -0
- package/dist/chunk-WD7BVW5Q.mjs +449 -0
- package/dist/index.d.mts +80 -0
- package/dist/index.d.ts +80 -0
- package/dist/index.js +473 -0
- package/dist/index.mjs +6 -0
- package/dist/react.d.mts +13 -0
- package/dist/react.d.ts +13 -0
- package/dist/react.js +512 -0
- package/dist/react.mjs +45 -0
- package/package.json +58 -0
package/dist/react.js
ADDED
|
@@ -0,0 +1,512 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/react.tsx
|
|
21
|
+
var react_exports = {};
|
|
22
|
+
__export(react_exports, {
|
|
23
|
+
ParticleNetworkBg: () => ParticleNetworkBg,
|
|
24
|
+
useParticleNetwork: () => useParticleNetwork
|
|
25
|
+
});
|
|
26
|
+
module.exports = __toCommonJS(react_exports);
|
|
27
|
+
var import_react = require("react");
|
|
28
|
+
|
|
29
|
+
// src/index.ts
|
|
30
|
+
var DEFAULT_CONFIG = {
|
|
31
|
+
particleCount: 100,
|
|
32
|
+
minRadius: 2,
|
|
33
|
+
maxRadius: 6,
|
|
34
|
+
particleColor: "#000000",
|
|
35
|
+
lineColor: "#000000",
|
|
36
|
+
lineWidth: 1,
|
|
37
|
+
lineOpacity: 0.2,
|
|
38
|
+
maxDistance: 150,
|
|
39
|
+
moveSpeed: 1,
|
|
40
|
+
backgroundColor: "#ffffff",
|
|
41
|
+
backgroundOpacity: 1,
|
|
42
|
+
particleOpacity: 1,
|
|
43
|
+
mouseRadius: 200,
|
|
44
|
+
mouseInteraction: true,
|
|
45
|
+
pulseEnabled: true,
|
|
46
|
+
pulseSpeed: 0,
|
|
47
|
+
depthEffectEnabled: true,
|
|
48
|
+
depthSpeed: 0.02,
|
|
49
|
+
gradientEnabled: false,
|
|
50
|
+
gradientType: "linear",
|
|
51
|
+
gradientColors: ["#667eea", "#764ba2"],
|
|
52
|
+
gradientSpeed: 0.02,
|
|
53
|
+
gradientMouseReaction: true,
|
|
54
|
+
gradientMouseInfluence: 0.5,
|
|
55
|
+
gradientAngle: 0,
|
|
56
|
+
gradientRadius: 1,
|
|
57
|
+
gradientSpin: false,
|
|
58
|
+
gradientFlowAngle: 45,
|
|
59
|
+
gradientOrbitRadius: 0.3
|
|
60
|
+
};
|
|
61
|
+
var HEX_COLOR_REGEX = /^#[0-9A-Fa-f]{6}$/;
|
|
62
|
+
var ParticleNetwork = class {
|
|
63
|
+
constructor(canvas, config = {}) {
|
|
64
|
+
this.particles = [];
|
|
65
|
+
this.animationId = null;
|
|
66
|
+
this.isRunning = false;
|
|
67
|
+
this.mousePosition = null;
|
|
68
|
+
this.pulseAngle = 0;
|
|
69
|
+
this.gradientAngle = 0;
|
|
70
|
+
this.gradientFlowOffset = 0;
|
|
71
|
+
this.gradientCenter = { x: 0, y: 0 };
|
|
72
|
+
this.smoothedMouseAngle = 0;
|
|
73
|
+
this.canvas = canvas;
|
|
74
|
+
const ctx = canvas.getContext("2d", { alpha: true });
|
|
75
|
+
if (!ctx) {
|
|
76
|
+
throw new Error("Could not initialize canvas context");
|
|
77
|
+
}
|
|
78
|
+
this.ctx = ctx;
|
|
79
|
+
this.config = this.validateConfig({ ...DEFAULT_CONFIG, ...config });
|
|
80
|
+
this.boundHandleResize = this.handleResize.bind(this);
|
|
81
|
+
this.boundHandleMouseMove = this.handleMouseMove.bind(this);
|
|
82
|
+
this.boundHandleMouseLeave = this.handleMouseLeave.bind(this);
|
|
83
|
+
this.handleResize();
|
|
84
|
+
this.createParticles();
|
|
85
|
+
this.setupEventListeners();
|
|
86
|
+
}
|
|
87
|
+
validateConfig(config) {
|
|
88
|
+
const numericParams = [
|
|
89
|
+
"particleCount",
|
|
90
|
+
"minRadius",
|
|
91
|
+
"maxRadius",
|
|
92
|
+
"lineWidth",
|
|
93
|
+
"lineOpacity",
|
|
94
|
+
"maxDistance",
|
|
95
|
+
"moveSpeed",
|
|
96
|
+
"backgroundOpacity",
|
|
97
|
+
"particleOpacity",
|
|
98
|
+
"mouseRadius",
|
|
99
|
+
"pulseSpeed",
|
|
100
|
+
"depthSpeed",
|
|
101
|
+
"gradientSpeed",
|
|
102
|
+
"gradientMouseInfluence",
|
|
103
|
+
"gradientAngle",
|
|
104
|
+
"gradientRadius",
|
|
105
|
+
"gradientFlowAngle",
|
|
106
|
+
"gradientOrbitRadius"
|
|
107
|
+
];
|
|
108
|
+
for (const param of numericParams) {
|
|
109
|
+
const val = config[param];
|
|
110
|
+
if (typeof val !== "number" || isNaN(val)) {
|
|
111
|
+
throw new Error(`Invalid ${param}: must be a number`);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
const booleanParams = [
|
|
115
|
+
"mouseInteraction",
|
|
116
|
+
"pulseEnabled",
|
|
117
|
+
"depthEffectEnabled",
|
|
118
|
+
"gradientEnabled",
|
|
119
|
+
"gradientMouseReaction",
|
|
120
|
+
"gradientSpin"
|
|
121
|
+
];
|
|
122
|
+
for (const param of booleanParams) {
|
|
123
|
+
if (typeof config[param] !== "boolean") {
|
|
124
|
+
throw new Error(`Invalid ${param}: must be a boolean`);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
const colorParams = [
|
|
128
|
+
"backgroundColor",
|
|
129
|
+
"particleColor",
|
|
130
|
+
"lineColor"
|
|
131
|
+
];
|
|
132
|
+
for (const param of colorParams) {
|
|
133
|
+
if (!HEX_COLOR_REGEX.test(config[param])) {
|
|
134
|
+
throw new Error(`Invalid ${param}: must be a valid hex color`);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
if (config.gradientEnabled) {
|
|
138
|
+
if (!Array.isArray(config.gradientColors) || config.gradientColors.length < 2) {
|
|
139
|
+
throw new Error(
|
|
140
|
+
"gradientColors must be an array of at least 2 hex colors"
|
|
141
|
+
);
|
|
142
|
+
}
|
|
143
|
+
for (const c of config.gradientColors) {
|
|
144
|
+
if (!HEX_COLOR_REGEX.test(c)) {
|
|
145
|
+
throw new Error(`Invalid gradient color: ${c} must be valid hex`);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
if (config.gradientType !== "linear" && config.gradientType !== "radial") {
|
|
149
|
+
throw new Error("gradientType must be 'linear' or 'radial'");
|
|
150
|
+
}
|
|
151
|
+
if (config.gradientStops) {
|
|
152
|
+
if (config.gradientStops.length !== config.gradientColors.length) {
|
|
153
|
+
throw new Error(
|
|
154
|
+
"gradientStops length must match gradientColors length"
|
|
155
|
+
);
|
|
156
|
+
}
|
|
157
|
+
for (let i = 0; i < config.gradientStops.length; i++) {
|
|
158
|
+
const s = config.gradientStops[i];
|
|
159
|
+
if (typeof s !== "number" || s < 0 || s > 1) {
|
|
160
|
+
throw new Error(`gradientStops[${i}] must be a number 0-1`);
|
|
161
|
+
}
|
|
162
|
+
if (i > 0 && s <= config.gradientStops[i - 1]) {
|
|
163
|
+
throw new Error("gradientStops must be strictly increasing");
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
return config;
|
|
169
|
+
}
|
|
170
|
+
setupEventListeners() {
|
|
171
|
+
window.addEventListener("resize", this.boundHandleResize);
|
|
172
|
+
this.canvas.addEventListener("mousemove", this.boundHandleMouseMove);
|
|
173
|
+
this.canvas.addEventListener("mouseleave", this.boundHandleMouseLeave);
|
|
174
|
+
}
|
|
175
|
+
cleanup() {
|
|
176
|
+
window.removeEventListener("resize", this.boundHandleResize);
|
|
177
|
+
this.canvas.removeEventListener("mousemove", this.boundHandleMouseMove);
|
|
178
|
+
this.canvas.removeEventListener("mouseleave", this.boundHandleMouseLeave);
|
|
179
|
+
this.stop();
|
|
180
|
+
}
|
|
181
|
+
handleMouseMove(e) {
|
|
182
|
+
const rect = this.canvas.getBoundingClientRect();
|
|
183
|
+
this.mousePosition = {
|
|
184
|
+
x: e.clientX - rect.left,
|
|
185
|
+
y: e.clientY - rect.top
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
handleMouseLeave() {
|
|
189
|
+
this.mousePosition = null;
|
|
190
|
+
}
|
|
191
|
+
handleResize() {
|
|
192
|
+
this.canvas.width = window.innerWidth;
|
|
193
|
+
this.canvas.height = window.innerHeight;
|
|
194
|
+
this.gradientCenter = {
|
|
195
|
+
x: this.canvas.width / 2,
|
|
196
|
+
y: this.canvas.height / 2
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
createParticles() {
|
|
200
|
+
this.particles = [];
|
|
201
|
+
for (let i = 0; i < this.config.particleCount; i++) {
|
|
202
|
+
const sizeRange = this.config.maxRadius - this.config.minRadius;
|
|
203
|
+
const randomSize = Math.random() * sizeRange + this.config.minRadius;
|
|
204
|
+
this.particles.push({
|
|
205
|
+
x: Math.random() * this.canvas.width,
|
|
206
|
+
y: Math.random() * this.canvas.height,
|
|
207
|
+
dx: (Math.random() - 0.5) * this.config.moveSpeed,
|
|
208
|
+
dy: (Math.random() - 0.5) * this.config.moveSpeed,
|
|
209
|
+
radius: randomSize,
|
|
210
|
+
z: Math.random(),
|
|
211
|
+
dz: (Math.random() - 0.5) * this.config.depthSpeed * 2
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
updateParticles() {
|
|
216
|
+
this.particles.forEach((particle) => {
|
|
217
|
+
if (this.config.depthEffectEnabled) {
|
|
218
|
+
particle.z += particle.dz;
|
|
219
|
+
if (particle.z <= 0) {
|
|
220
|
+
particle.z = 0;
|
|
221
|
+
particle.dz = Math.abs(particle.dz);
|
|
222
|
+
} else if (particle.z >= 1) {
|
|
223
|
+
particle.z = 1;
|
|
224
|
+
particle.dz = -Math.abs(particle.dz);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
if (this.config.pulseEnabled) {
|
|
228
|
+
this.pulseAngle += this.config.pulseSpeed;
|
|
229
|
+
const pulseScale = Math.sin(this.pulseAngle) * 0.5 + 1;
|
|
230
|
+
particle.currentRadius = particle.radius * pulseScale;
|
|
231
|
+
} else {
|
|
232
|
+
particle.currentRadius = particle.radius;
|
|
233
|
+
}
|
|
234
|
+
if (this.config.depthEffectEnabled) {
|
|
235
|
+
const depthScale = 0.4 + 0.6 * particle.z;
|
|
236
|
+
particle.currentRadius = (particle.currentRadius ?? particle.radius) * depthScale;
|
|
237
|
+
}
|
|
238
|
+
particle.x += particle.dx;
|
|
239
|
+
particle.y += particle.dy;
|
|
240
|
+
if (this.config.mouseInteraction && this.mousePosition) {
|
|
241
|
+
const dx = this.mousePosition.x - particle.x;
|
|
242
|
+
const dy = this.mousePosition.y - particle.y;
|
|
243
|
+
const distance = Math.sqrt(dx * dx + dy * dy);
|
|
244
|
+
if (distance < this.config.mouseRadius) {
|
|
245
|
+
const force = (this.config.mouseRadius - distance) / this.config.mouseRadius;
|
|
246
|
+
const angle = Math.atan2(dy, dx);
|
|
247
|
+
const repelX = Math.cos(angle) * force * 0.5;
|
|
248
|
+
const repelY = Math.sin(angle) * force * 0.5;
|
|
249
|
+
particle.dx -= repelX;
|
|
250
|
+
particle.dy -= repelY;
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
if (particle.x < 0 || particle.x > this.canvas.width) {
|
|
254
|
+
particle.dx = -particle.dx;
|
|
255
|
+
}
|
|
256
|
+
if (particle.y < 0 || particle.y > this.canvas.height) {
|
|
257
|
+
particle.dy = -particle.dy;
|
|
258
|
+
}
|
|
259
|
+
const speed = Math.sqrt(
|
|
260
|
+
particle.dx * particle.dx + particle.dy * particle.dy
|
|
261
|
+
);
|
|
262
|
+
if (speed > this.config.moveSpeed) {
|
|
263
|
+
particle.dx = particle.dx / speed * this.config.moveSpeed;
|
|
264
|
+
particle.dy = particle.dy / speed * this.config.moveSpeed;
|
|
265
|
+
}
|
|
266
|
+
});
|
|
267
|
+
}
|
|
268
|
+
drawParticles() {
|
|
269
|
+
this.ctx.fillStyle = this.config.particleColor;
|
|
270
|
+
this.particles.forEach((particle) => {
|
|
271
|
+
let opacity = this.config.particleOpacity;
|
|
272
|
+
if (this.config.depthEffectEnabled) {
|
|
273
|
+
opacity *= 0.6 + 0.4 * particle.z;
|
|
274
|
+
}
|
|
275
|
+
this.ctx.globalAlpha = opacity;
|
|
276
|
+
this.ctx.beginPath();
|
|
277
|
+
this.ctx.arc(
|
|
278
|
+
particle.x,
|
|
279
|
+
particle.y,
|
|
280
|
+
particle.currentRadius ?? particle.radius,
|
|
281
|
+
0,
|
|
282
|
+
Math.PI * 2
|
|
283
|
+
);
|
|
284
|
+
this.ctx.fill();
|
|
285
|
+
});
|
|
286
|
+
this.ctx.globalAlpha = 1;
|
|
287
|
+
}
|
|
288
|
+
drawConnections() {
|
|
289
|
+
for (let i = 0; i < this.particles.length; i++) {
|
|
290
|
+
for (let j = i + 1; j < this.particles.length; j++) {
|
|
291
|
+
const dx = this.particles[i].x - this.particles[j].x;
|
|
292
|
+
const dy = this.particles[i].y - this.particles[j].y;
|
|
293
|
+
const distance = Math.sqrt(dx * dx + dy * dy);
|
|
294
|
+
if (distance < this.config.maxDistance) {
|
|
295
|
+
const opacity = 1 - distance / this.config.maxDistance;
|
|
296
|
+
const color = this.hexToRgb(this.config.lineColor);
|
|
297
|
+
this.ctx.beginPath();
|
|
298
|
+
this.ctx.strokeStyle = `rgba(${color}, ${opacity * this.config.lineOpacity})`;
|
|
299
|
+
this.ctx.lineWidth = this.config.lineWidth;
|
|
300
|
+
this.ctx.moveTo(this.particles[i].x, this.particles[i].y);
|
|
301
|
+
this.ctx.lineTo(this.particles[j].x, this.particles[j].y);
|
|
302
|
+
this.ctx.stroke();
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
drawBackground() {
|
|
308
|
+
this.ctx.globalAlpha = this.config.backgroundOpacity;
|
|
309
|
+
if (this.config.gradientEnabled) {
|
|
310
|
+
const w = this.canvas.width;
|
|
311
|
+
const h = this.canvas.height;
|
|
312
|
+
const colors = this.config.gradientColors;
|
|
313
|
+
const stops = this.config.gradientStops ?? colors.map((_, i) => i / (colors.length - 1));
|
|
314
|
+
if (this.config.gradientType === "linear") {
|
|
315
|
+
const baseAngleRad = this.config.gradientFlowAngle * Math.PI / 180;
|
|
316
|
+
const diag = Math.sqrt(w * w + h * h);
|
|
317
|
+
let angle;
|
|
318
|
+
let offsetX = 0;
|
|
319
|
+
let offsetY = 0;
|
|
320
|
+
if (this.config.gradientSpin) {
|
|
321
|
+
this.gradientAngle += this.config.gradientSpeed;
|
|
322
|
+
angle = this.gradientAngle;
|
|
323
|
+
} else {
|
|
324
|
+
this.gradientFlowOffset = (this.gradientFlowOffset + this.config.gradientSpeed) % (Math.PI * 2);
|
|
325
|
+
const t = this.gradientFlowOffset / (Math.PI * 2);
|
|
326
|
+
offsetX = Math.cos(baseAngleRad) * t * diag;
|
|
327
|
+
offsetY = Math.sin(baseAngleRad) * t * diag;
|
|
328
|
+
angle = baseAngleRad;
|
|
329
|
+
}
|
|
330
|
+
const cos = Math.cos(angle);
|
|
331
|
+
const sin = Math.sin(angle);
|
|
332
|
+
const lineLen = this.config.gradientSpin ? diag / 2 : diag * 1.5;
|
|
333
|
+
const cx = w / 2 + offsetX;
|
|
334
|
+
const cy = h / 2 + offsetY;
|
|
335
|
+
const x1 = cx - cos * lineLen;
|
|
336
|
+
const y1 = cy - sin * lineLen;
|
|
337
|
+
const x2 = cx + cos * lineLen;
|
|
338
|
+
const y2 = cy + sin * lineLen;
|
|
339
|
+
const gradient = this.ctx.createLinearGradient(x1, y1, x2, y2);
|
|
340
|
+
if (this.config.gradientSpin) {
|
|
341
|
+
colors.forEach((c, i) => gradient.addColorStop(stops[i], c));
|
|
342
|
+
} else {
|
|
343
|
+
const extColors = [...colors, colors[0]];
|
|
344
|
+
for (let rep = 0; rep < 3; rep++) {
|
|
345
|
+
extColors.forEach((c, i) => {
|
|
346
|
+
gradient.addColorStop((rep + i / (extColors.length - 1)) / 3, c);
|
|
347
|
+
});
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
this.ctx.fillStyle = gradient;
|
|
351
|
+
} else {
|
|
352
|
+
const cx = w / 2;
|
|
353
|
+
const cy = h / 2;
|
|
354
|
+
const orbitR = Math.min(w, h) * this.config.gradientOrbitRadius;
|
|
355
|
+
let targetX;
|
|
356
|
+
let targetY;
|
|
357
|
+
this.gradientAngle += this.config.gradientSpeed;
|
|
358
|
+
targetX = cx + Math.cos(this.gradientAngle) * orbitR;
|
|
359
|
+
targetY = cy + Math.sin(this.gradientAngle) * orbitR;
|
|
360
|
+
if (this.config.gradientMouseReaction && this.mousePosition) {
|
|
361
|
+
const influence = this.config.gradientMouseInfluence;
|
|
362
|
+
targetX = targetX + (this.mousePosition.x - targetX) * influence;
|
|
363
|
+
targetY = targetY + (this.mousePosition.y - targetY) * influence;
|
|
364
|
+
}
|
|
365
|
+
const lerpFactor = 0.03;
|
|
366
|
+
this.gradientCenter.x += (targetX - this.gradientCenter.x) * lerpFactor;
|
|
367
|
+
this.gradientCenter.y += (targetY - this.gradientCenter.y) * lerpFactor;
|
|
368
|
+
this.gradientFlowOffset = (this.gradientFlowOffset + this.config.gradientSpeed) % (Math.PI * 2);
|
|
369
|
+
const t = this.gradientFlowOffset / (Math.PI * 2);
|
|
370
|
+
const r = Math.max(w, h) * this.config.gradientRadius * 2;
|
|
371
|
+
const gradient = this.ctx.createRadialGradient(
|
|
372
|
+
this.gradientCenter.x,
|
|
373
|
+
this.gradientCenter.y,
|
|
374
|
+
0,
|
|
375
|
+
this.gradientCenter.x,
|
|
376
|
+
this.gradientCenter.y,
|
|
377
|
+
r
|
|
378
|
+
);
|
|
379
|
+
const extColors = [...colors, colors[0]];
|
|
380
|
+
const entries = [];
|
|
381
|
+
for (let rep = 0; rep < 3; rep++) {
|
|
382
|
+
extColors.forEach((c, i) => {
|
|
383
|
+
const base = (rep + i / (extColors.length - 1)) / 3;
|
|
384
|
+
entries.push({ stop: (base + t) % 1, color: c });
|
|
385
|
+
});
|
|
386
|
+
}
|
|
387
|
+
entries.sort((a, b) => a.stop - b.stop);
|
|
388
|
+
if (entries[0].stop > 1e-3) {
|
|
389
|
+
entries.unshift({ stop: 0, color: entries[entries.length - 1].color });
|
|
390
|
+
}
|
|
391
|
+
if (entries[entries.length - 1].stop < 0.999) {
|
|
392
|
+
entries.push({ stop: 1, color: entries[0].color });
|
|
393
|
+
}
|
|
394
|
+
entries.forEach((e) => gradient.addColorStop(e.stop, e.color));
|
|
395
|
+
this.ctx.fillStyle = gradient;
|
|
396
|
+
}
|
|
397
|
+
} else {
|
|
398
|
+
this.ctx.fillStyle = this.config.backgroundColor;
|
|
399
|
+
}
|
|
400
|
+
this.ctx.fillRect(0, 0, this.canvas.width, this.canvas.height);
|
|
401
|
+
this.ctx.globalAlpha = 1;
|
|
402
|
+
}
|
|
403
|
+
hexToRgb(hex) {
|
|
404
|
+
hex = hex.replace(/^#/, "");
|
|
405
|
+
if (hex.length === 3) {
|
|
406
|
+
hex = hex.split("").map((c) => c + c).join("");
|
|
407
|
+
}
|
|
408
|
+
const result = /^([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
|
|
409
|
+
if (!result) return "255,255,255";
|
|
410
|
+
return result.slice(1).map((n) => parseInt(n, 16)).join(",");
|
|
411
|
+
}
|
|
412
|
+
stop() {
|
|
413
|
+
this.isRunning = false;
|
|
414
|
+
if (this.animationId !== null) {
|
|
415
|
+
cancelAnimationFrame(this.animationId);
|
|
416
|
+
this.animationId = null;
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
start() {
|
|
420
|
+
if (!this.isRunning) {
|
|
421
|
+
this.isRunning = true;
|
|
422
|
+
this.animate();
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
animate() {
|
|
426
|
+
this.drawBackground();
|
|
427
|
+
this.updateParticles();
|
|
428
|
+
this.drawParticles();
|
|
429
|
+
this.drawConnections();
|
|
430
|
+
if (this.isRunning) {
|
|
431
|
+
this.animationId = requestAnimationFrame(() => this.animate());
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
updateConfig(property, value) {
|
|
435
|
+
this.config[property] = value;
|
|
436
|
+
if (property === "particleCount") {
|
|
437
|
+
this.createParticles();
|
|
438
|
+
}
|
|
439
|
+
if (property === "moveSpeed") {
|
|
440
|
+
this.particles.forEach((particle) => {
|
|
441
|
+
const currentSpeed = Math.sqrt(
|
|
442
|
+
particle.dx * particle.dx + particle.dy * particle.dy
|
|
443
|
+
);
|
|
444
|
+
if (currentSpeed > 0) {
|
|
445
|
+
particle.dx = particle.dx / currentSpeed * value;
|
|
446
|
+
particle.dy = particle.dy / currentSpeed * value;
|
|
447
|
+
}
|
|
448
|
+
});
|
|
449
|
+
}
|
|
450
|
+
if (property === "minRadius" || property === "maxRadius") {
|
|
451
|
+
this.particles.forEach((particle) => {
|
|
452
|
+
const sizeRange = this.config.maxRadius - this.config.minRadius;
|
|
453
|
+
const randomSize = Math.random() * sizeRange + this.config.minRadius;
|
|
454
|
+
particle.radius = randomSize;
|
|
455
|
+
});
|
|
456
|
+
}
|
|
457
|
+
if (property === "depthSpeed") {
|
|
458
|
+
this.particles.forEach((particle) => {
|
|
459
|
+
const sign = particle.dz >= 0 ? 1 : -1;
|
|
460
|
+
particle.dz = sign * (Math.random() * 0.5 + 0.5) * value * 2;
|
|
461
|
+
});
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
reset(defaults) {
|
|
465
|
+
this.config = this.validateConfig({
|
|
466
|
+
...DEFAULT_CONFIG,
|
|
467
|
+
...defaults
|
|
468
|
+
});
|
|
469
|
+
this.createParticles();
|
|
470
|
+
this.stop();
|
|
471
|
+
this.start();
|
|
472
|
+
}
|
|
473
|
+
};
|
|
474
|
+
|
|
475
|
+
// src/react.tsx
|
|
476
|
+
var import_jsx_runtime = require("react/jsx-runtime");
|
|
477
|
+
function useParticleNetwork(config) {
|
|
478
|
+
const canvasRef = (0, import_react.useRef)(null);
|
|
479
|
+
const instanceRef = (0, import_react.useRef)(null);
|
|
480
|
+
(0, import_react.useEffect)(() => {
|
|
481
|
+
const canvas = canvasRef.current;
|
|
482
|
+
if (!canvas) return;
|
|
483
|
+
const instance = new ParticleNetwork(canvas, config);
|
|
484
|
+
instanceRef.current = instance;
|
|
485
|
+
instance.start();
|
|
486
|
+
return () => {
|
|
487
|
+
instance.cleanup();
|
|
488
|
+
instanceRef.current = null;
|
|
489
|
+
};
|
|
490
|
+
}, []);
|
|
491
|
+
return canvasRef;
|
|
492
|
+
}
|
|
493
|
+
function ParticleNetworkBg({
|
|
494
|
+
config,
|
|
495
|
+
style,
|
|
496
|
+
className
|
|
497
|
+
}) {
|
|
498
|
+
const canvasRef = useParticleNetwork(config);
|
|
499
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
500
|
+
"canvas",
|
|
501
|
+
{
|
|
502
|
+
ref: canvasRef,
|
|
503
|
+
style: { display: "block", ...style },
|
|
504
|
+
className
|
|
505
|
+
}
|
|
506
|
+
);
|
|
507
|
+
}
|
|
508
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
509
|
+
0 && (module.exports = {
|
|
510
|
+
ParticleNetworkBg,
|
|
511
|
+
useParticleNetwork
|
|
512
|
+
});
|
package/dist/react.mjs
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ParticleNetwork
|
|
3
|
+
} from "./chunk-WD7BVW5Q.mjs";
|
|
4
|
+
|
|
5
|
+
// src/react.tsx
|
|
6
|
+
import {
|
|
7
|
+
useEffect,
|
|
8
|
+
useRef
|
|
9
|
+
} from "react";
|
|
10
|
+
import { jsx } from "react/jsx-runtime";
|
|
11
|
+
function useParticleNetwork(config) {
|
|
12
|
+
const canvasRef = useRef(null);
|
|
13
|
+
const instanceRef = useRef(null);
|
|
14
|
+
useEffect(() => {
|
|
15
|
+
const canvas = canvasRef.current;
|
|
16
|
+
if (!canvas) return;
|
|
17
|
+
const instance = new ParticleNetwork(canvas, config);
|
|
18
|
+
instanceRef.current = instance;
|
|
19
|
+
instance.start();
|
|
20
|
+
return () => {
|
|
21
|
+
instance.cleanup();
|
|
22
|
+
instanceRef.current = null;
|
|
23
|
+
};
|
|
24
|
+
}, []);
|
|
25
|
+
return canvasRef;
|
|
26
|
+
}
|
|
27
|
+
function ParticleNetworkBg({
|
|
28
|
+
config,
|
|
29
|
+
style,
|
|
30
|
+
className
|
|
31
|
+
}) {
|
|
32
|
+
const canvasRef = useParticleNetwork(config);
|
|
33
|
+
return /* @__PURE__ */ jsx(
|
|
34
|
+
"canvas",
|
|
35
|
+
{
|
|
36
|
+
ref: canvasRef,
|
|
37
|
+
style: { display: "block", ...style },
|
|
38
|
+
className
|
|
39
|
+
}
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
export {
|
|
43
|
+
ParticleNetworkBg,
|
|
44
|
+
useParticleNetwork
|
|
45
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "particle-network-bg",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "Interactive particle network animation for backgrounds",
|
|
5
|
+
"main": "./dist/index.js",
|
|
6
|
+
"module": "./dist/index.mjs",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "https://github.com/aliotadi/particle-network-bg.git"
|
|
11
|
+
},
|
|
12
|
+
"homepage": "https://aliotadi.github.io/particle-network-bg/",
|
|
13
|
+
"bugs": {
|
|
14
|
+
"url": "https://github.com/aliotadi/particle-network-bg/issues"
|
|
15
|
+
},
|
|
16
|
+
"keywords": [
|
|
17
|
+
"particle",
|
|
18
|
+
"canvas",
|
|
19
|
+
"animation",
|
|
20
|
+
"background",
|
|
21
|
+
"network",
|
|
22
|
+
"react"
|
|
23
|
+
],
|
|
24
|
+
"license": "MIT",
|
|
25
|
+
"exports": {
|
|
26
|
+
".": {
|
|
27
|
+
"types": "./dist/index.d.ts",
|
|
28
|
+
"import": "./dist/index.mjs",
|
|
29
|
+
"require": "./dist/index.js"
|
|
30
|
+
},
|
|
31
|
+
"./react": {
|
|
32
|
+
"types": "./dist/react.d.ts",
|
|
33
|
+
"import": "./dist/react.mjs",
|
|
34
|
+
"require": "./dist/react.js"
|
|
35
|
+
}
|
|
36
|
+
},
|
|
37
|
+
"peerDependencies": {
|
|
38
|
+
"react": ">=18.0.0"
|
|
39
|
+
},
|
|
40
|
+
"peerDependenciesMeta": {
|
|
41
|
+
"react": {
|
|
42
|
+
"optional": true
|
|
43
|
+
}
|
|
44
|
+
},
|
|
45
|
+
"files": [
|
|
46
|
+
"dist"
|
|
47
|
+
],
|
|
48
|
+
"scripts": {
|
|
49
|
+
"build": "tsup",
|
|
50
|
+
"dev": "tsup --watch"
|
|
51
|
+
},
|
|
52
|
+
"devDependencies": {
|
|
53
|
+
"@types/react": "^19.2.14",
|
|
54
|
+
"tsup": "^8.0.0",
|
|
55
|
+
"typescript": "^5.0.0"
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|