canvasparticles-js 4.5.3 → 4.5.5
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/dist/index.cjs +50 -50
- package/dist/index.mjs +50 -50
- package/dist/index.umd.js +1 -1
- package/package.json +4 -4
- package/src/index.ts +43 -36
package/dist/index.cjs
CHANGED
|
@@ -21,25 +21,21 @@ function parseNumericOption(name, value, defaultValue, clamp) {
|
|
|
21
21
|
// https://github.com/Khoeckman/canvasparticles-js/blob/main/LICENSE
|
|
22
22
|
const TWO_PI = 2 * Math.PI;
|
|
23
23
|
/** Extremely fast, simple 32‑bit PRNG */
|
|
24
|
-
function
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
t = Math.imul(t ^ (t >>> 15), t | 1);
|
|
31
|
-
t ^= t + Math.imul(t ^ (t >>> 7), t | 61);
|
|
32
|
-
return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
|
|
33
|
-
},
|
|
24
|
+
function mulberry32(seed) {
|
|
25
|
+
return function () {
|
|
26
|
+
let t = (seed += 0x6d2b79f5);
|
|
27
|
+
t = Math.imul(t ^ (t >>> 15), t | 1);
|
|
28
|
+
t ^= t + Math.imul(t ^ (t >>> 7), t | 61);
|
|
29
|
+
return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
|
|
34
30
|
};
|
|
35
31
|
}
|
|
36
|
-
// Mulberry32 is
|
|
32
|
+
// Mulberry32 is x4 faster than Math.random()
|
|
37
33
|
// Benchmark: https://jsbm.dev/muLCWR9RJCbmy
|
|
38
34
|
// Spectral test: /demo/mulberry32.html
|
|
39
|
-
const prng =
|
|
35
|
+
const prng = mulberry32((Math.random() * 4294967296) | 0);
|
|
40
36
|
class CanvasParticles {
|
|
41
37
|
/** Version of the library, injected via Rollup replace plugin. */
|
|
42
|
-
static version = "4.5.
|
|
38
|
+
static version = "4.5.5";
|
|
43
39
|
static MAX_DT = 1000 / 30; // milliseconds between updates @ 30 FPS
|
|
44
40
|
static BASE_DT = 1000 / 60; // milliseconds between updates @ 60 FPS
|
|
45
41
|
/** Defines mouse interaction types with the particles */
|
|
@@ -162,10 +158,12 @@ class CanvasParticles {
|
|
|
162
158
|
}
|
|
163
159
|
/** Resize the canvas and update particles accordingly */
|
|
164
160
|
#resizeCanvas(dpr = window.devicePixelRatio || 1) {
|
|
161
|
+
if (dpr < 1)
|
|
162
|
+
dpr = 1;
|
|
165
163
|
const width = (this.canvas.width = this.canvas.rect.width * dpr);
|
|
166
164
|
const height = (this.canvas.height = this.canvas.rect.height * dpr);
|
|
167
165
|
// Must be set every time width or height changes because scale is removed
|
|
168
|
-
if (dpr
|
|
166
|
+
if (dpr > 1)
|
|
169
167
|
this.ctx.scale(dpr, dpr);
|
|
170
168
|
// Hide the mouse when resizing because it must be outside the viewport to do so
|
|
171
169
|
this.mouseX = Infinity;
|
|
@@ -442,11 +440,12 @@ class CanvasParticles {
|
|
|
442
440
|
/** Draw the particles on the canvas */
|
|
443
441
|
#renderParticles() {
|
|
444
442
|
const ctx = this.ctx;
|
|
443
|
+
const dpr = window.devicePixelRatio || 1;
|
|
445
444
|
for (const p of this.particles) {
|
|
446
445
|
if (!p.isVisible)
|
|
447
446
|
continue;
|
|
448
447
|
// Draw particles smaller than 1px as a square instead of a circle for performance
|
|
449
|
-
if (p.size > 1) {
|
|
448
|
+
if (p.size > 1 / dpr) {
|
|
450
449
|
// Draw circle
|
|
451
450
|
ctx.beginPath();
|
|
452
451
|
ctx.arc(p.x, p.y, p.size, 0, TWO_PI);
|
|
@@ -494,6 +493,7 @@ class CanvasParticles {
|
|
|
494
493
|
const halfMaxDistSq = (maxDist / 2) ** 2;
|
|
495
494
|
const invCellSize = 1 / maxDist;
|
|
496
495
|
const stride = Math.ceil(this.width * invCellSize);
|
|
496
|
+
const rows = Math.ceil(this.height * invCellSize);
|
|
497
497
|
const drawAll = maxDist >= Math.min(this.canvas.width, this.canvas.height);
|
|
498
498
|
const maxWorkPerParticle = maxDistSq * this.option.particles.maxWork;
|
|
499
499
|
const alpha = this.color.alpha;
|
|
@@ -568,22 +568,22 @@ class CanvasParticles {
|
|
|
568
568
|
let cell;
|
|
569
569
|
if ((cell = grid.get(key + 1)))
|
|
570
570
|
renderConnectionsToCell(cell, pa); // (+1, 0)
|
|
571
|
-
if (
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
571
|
+
if (allowWork) {
|
|
572
|
+
if ((cell = grid.get(key + stride)))
|
|
573
|
+
renderConnectionsToCell(cell, pa); // (0, +1)
|
|
574
|
+
if (allowWork) {
|
|
575
|
+
if ((cell = grid.get(key + stride + 1)))
|
|
576
|
+
renderConnectionsToCell(cell, pa); // (+1, +1)
|
|
577
|
+
if (allowWork) {
|
|
578
|
+
if ((cell = grid.get(key + stride - 1)))
|
|
579
|
+
renderConnectionsToCell(cell, pa); // (-1, +1)
|
|
580
|
+
if (allowWork) {
|
|
581
|
+
if (cellX >= 0 && cellY >= 0 && cellX < stride - 2 && cellY < rows - 2 && (cell = grid.get(key)))
|
|
582
|
+
renderConnectionsToOwnCell(cell || [], a, pa);
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
587
|
// Next iteration
|
|
588
588
|
if (++a >= len)
|
|
589
589
|
break;
|
|
@@ -596,22 +596,22 @@ class CanvasParticles {
|
|
|
596
596
|
key = cellX + Math.imul(cellY, stride);
|
|
597
597
|
if ((cell = grid.get(key + stride + 1)))
|
|
598
598
|
renderConnectionsToCell(cell, pa); // (+1, +1)
|
|
599
|
-
if (
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
599
|
+
if (allowWork) {
|
|
600
|
+
if ((cell = grid.get(key + stride - 1)))
|
|
601
|
+
renderConnectionsToCell(cell, pa); // (-1, +1)
|
|
602
|
+
if (allowWork) {
|
|
603
|
+
if ((cell = grid.get(key + 1)))
|
|
604
|
+
renderConnectionsToCell(cell, pa); // (+1, 0)
|
|
605
|
+
if (allowWork) {
|
|
606
|
+
if ((cell = grid.get(key + stride)))
|
|
607
|
+
renderConnectionsToCell(cell, pa); // (0, +1)
|
|
608
|
+
if (allowWork) {
|
|
609
|
+
if (cellX >= 0 && cellY >= 0 && cellX < stride - 2 && cellY < rows - 2 && (cell = grid.get(key)))
|
|
610
|
+
renderConnectionsToOwnCell(cell || [], a, pa);
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
615
|
// Next iteration
|
|
616
616
|
if (++a >= len)
|
|
617
617
|
break;
|
|
@@ -630,7 +630,7 @@ class CanvasParticles {
|
|
|
630
630
|
renderConnectionsToCell(cell, pa); // (+1, 0)
|
|
631
631
|
if (!allowWork)
|
|
632
632
|
continue;
|
|
633
|
-
if (cellX >= 0 && cellY >= 0 && cellX < stride - 2 && (cell = grid.get(key)))
|
|
633
|
+
if (cellX >= 0 && cellY >= 0 && cellX < stride - 2 && cellY < rows - 2 && (cell = grid.get(key)))
|
|
634
634
|
renderConnectionsToOwnCell(cell || [], a, pa);
|
|
635
635
|
if (!allowWork)
|
|
636
636
|
continue;
|
|
@@ -700,7 +700,7 @@ class CanvasParticles {
|
|
|
700
700
|
}
|
|
701
701
|
/** Clear the canvas and render the particles and their connections onto the canvas */
|
|
702
702
|
#render() {
|
|
703
|
-
this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
|
|
703
|
+
this.ctx.clearRect(0, 0, this.canvas.rect.width, this.canvas.rect.height);
|
|
704
704
|
this.ctx.globalAlpha = this.color.alpha;
|
|
705
705
|
this.ctx.fillStyle = this.color.hex;
|
|
706
706
|
this.ctx.strokeStyle = this.color.hex;
|
|
@@ -740,7 +740,7 @@ class CanvasParticles {
|
|
|
740
740
|
this.enableAnimating = false;
|
|
741
741
|
this.isAnimating = false;
|
|
742
742
|
if (clear !== false)
|
|
743
|
-
this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
|
|
743
|
+
this.ctx.clearRect(0, 0, this.canvas.rect.width, this.canvas.rect.height);
|
|
744
744
|
return true;
|
|
745
745
|
}
|
|
746
746
|
/** Gracefully destroy the instance and remove the canvas element */
|
package/dist/index.mjs
CHANGED
|
@@ -19,25 +19,21 @@ function parseNumericOption(name, value, defaultValue, clamp) {
|
|
|
19
19
|
// https://github.com/Khoeckman/canvasparticles-js/blob/main/LICENSE
|
|
20
20
|
const TWO_PI = 2 * Math.PI;
|
|
21
21
|
/** Extremely fast, simple 32‑bit PRNG */
|
|
22
|
-
function
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
t = Math.imul(t ^ (t >>> 15), t | 1);
|
|
29
|
-
t ^= t + Math.imul(t ^ (t >>> 7), t | 61);
|
|
30
|
-
return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
|
|
31
|
-
},
|
|
22
|
+
function mulberry32(seed) {
|
|
23
|
+
return function () {
|
|
24
|
+
let t = (seed += 0x6d2b79f5);
|
|
25
|
+
t = Math.imul(t ^ (t >>> 15), t | 1);
|
|
26
|
+
t ^= t + Math.imul(t ^ (t >>> 7), t | 61);
|
|
27
|
+
return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
|
|
32
28
|
};
|
|
33
29
|
}
|
|
34
|
-
// Mulberry32 is
|
|
30
|
+
// Mulberry32 is x4 faster than Math.random()
|
|
35
31
|
// Benchmark: https://jsbm.dev/muLCWR9RJCbmy
|
|
36
32
|
// Spectral test: /demo/mulberry32.html
|
|
37
|
-
const prng =
|
|
33
|
+
const prng = mulberry32((Math.random() * 4294967296) | 0);
|
|
38
34
|
class CanvasParticles {
|
|
39
35
|
/** Version of the library, injected via Rollup replace plugin. */
|
|
40
|
-
static version = "4.5.
|
|
36
|
+
static version = "4.5.5";
|
|
41
37
|
static MAX_DT = 1000 / 30; // milliseconds between updates @ 30 FPS
|
|
42
38
|
static BASE_DT = 1000 / 60; // milliseconds between updates @ 60 FPS
|
|
43
39
|
/** Defines mouse interaction types with the particles */
|
|
@@ -160,10 +156,12 @@ class CanvasParticles {
|
|
|
160
156
|
}
|
|
161
157
|
/** Resize the canvas and update particles accordingly */
|
|
162
158
|
#resizeCanvas(dpr = window.devicePixelRatio || 1) {
|
|
159
|
+
if (dpr < 1)
|
|
160
|
+
dpr = 1;
|
|
163
161
|
const width = (this.canvas.width = this.canvas.rect.width * dpr);
|
|
164
162
|
const height = (this.canvas.height = this.canvas.rect.height * dpr);
|
|
165
163
|
// Must be set every time width or height changes because scale is removed
|
|
166
|
-
if (dpr
|
|
164
|
+
if (dpr > 1)
|
|
167
165
|
this.ctx.scale(dpr, dpr);
|
|
168
166
|
// Hide the mouse when resizing because it must be outside the viewport to do so
|
|
169
167
|
this.mouseX = Infinity;
|
|
@@ -440,11 +438,12 @@ class CanvasParticles {
|
|
|
440
438
|
/** Draw the particles on the canvas */
|
|
441
439
|
#renderParticles() {
|
|
442
440
|
const ctx = this.ctx;
|
|
441
|
+
const dpr = window.devicePixelRatio || 1;
|
|
443
442
|
for (const p of this.particles) {
|
|
444
443
|
if (!p.isVisible)
|
|
445
444
|
continue;
|
|
446
445
|
// Draw particles smaller than 1px as a square instead of a circle for performance
|
|
447
|
-
if (p.size > 1) {
|
|
446
|
+
if (p.size > 1 / dpr) {
|
|
448
447
|
// Draw circle
|
|
449
448
|
ctx.beginPath();
|
|
450
449
|
ctx.arc(p.x, p.y, p.size, 0, TWO_PI);
|
|
@@ -492,6 +491,7 @@ class CanvasParticles {
|
|
|
492
491
|
const halfMaxDistSq = (maxDist / 2) ** 2;
|
|
493
492
|
const invCellSize = 1 / maxDist;
|
|
494
493
|
const stride = Math.ceil(this.width * invCellSize);
|
|
494
|
+
const rows = Math.ceil(this.height * invCellSize);
|
|
495
495
|
const drawAll = maxDist >= Math.min(this.canvas.width, this.canvas.height);
|
|
496
496
|
const maxWorkPerParticle = maxDistSq * this.option.particles.maxWork;
|
|
497
497
|
const alpha = this.color.alpha;
|
|
@@ -566,22 +566,22 @@ class CanvasParticles {
|
|
|
566
566
|
let cell;
|
|
567
567
|
if ((cell = grid.get(key + 1)))
|
|
568
568
|
renderConnectionsToCell(cell, pa); // (+1, 0)
|
|
569
|
-
if (
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
569
|
+
if (allowWork) {
|
|
570
|
+
if ((cell = grid.get(key + stride)))
|
|
571
|
+
renderConnectionsToCell(cell, pa); // (0, +1)
|
|
572
|
+
if (allowWork) {
|
|
573
|
+
if ((cell = grid.get(key + stride + 1)))
|
|
574
|
+
renderConnectionsToCell(cell, pa); // (+1, +1)
|
|
575
|
+
if (allowWork) {
|
|
576
|
+
if ((cell = grid.get(key + stride - 1)))
|
|
577
|
+
renderConnectionsToCell(cell, pa); // (-1, +1)
|
|
578
|
+
if (allowWork) {
|
|
579
|
+
if (cellX >= 0 && cellY >= 0 && cellX < stride - 2 && cellY < rows - 2 && (cell = grid.get(key)))
|
|
580
|
+
renderConnectionsToOwnCell(cell || [], a, pa);
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
585
|
// Next iteration
|
|
586
586
|
if (++a >= len)
|
|
587
587
|
break;
|
|
@@ -594,22 +594,22 @@ class CanvasParticles {
|
|
|
594
594
|
key = cellX + Math.imul(cellY, stride);
|
|
595
595
|
if ((cell = grid.get(key + stride + 1)))
|
|
596
596
|
renderConnectionsToCell(cell, pa); // (+1, +1)
|
|
597
|
-
if (
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
597
|
+
if (allowWork) {
|
|
598
|
+
if ((cell = grid.get(key + stride - 1)))
|
|
599
|
+
renderConnectionsToCell(cell, pa); // (-1, +1)
|
|
600
|
+
if (allowWork) {
|
|
601
|
+
if ((cell = grid.get(key + 1)))
|
|
602
|
+
renderConnectionsToCell(cell, pa); // (+1, 0)
|
|
603
|
+
if (allowWork) {
|
|
604
|
+
if ((cell = grid.get(key + stride)))
|
|
605
|
+
renderConnectionsToCell(cell, pa); // (0, +1)
|
|
606
|
+
if (allowWork) {
|
|
607
|
+
if (cellX >= 0 && cellY >= 0 && cellX < stride - 2 && cellY < rows - 2 && (cell = grid.get(key)))
|
|
608
|
+
renderConnectionsToOwnCell(cell || [], a, pa);
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
613
|
// Next iteration
|
|
614
614
|
if (++a >= len)
|
|
615
615
|
break;
|
|
@@ -628,7 +628,7 @@ class CanvasParticles {
|
|
|
628
628
|
renderConnectionsToCell(cell, pa); // (+1, 0)
|
|
629
629
|
if (!allowWork)
|
|
630
630
|
continue;
|
|
631
|
-
if (cellX >= 0 && cellY >= 0 && cellX < stride - 2 && (cell = grid.get(key)))
|
|
631
|
+
if (cellX >= 0 && cellY >= 0 && cellX < stride - 2 && cellY < rows - 2 && (cell = grid.get(key)))
|
|
632
632
|
renderConnectionsToOwnCell(cell || [], a, pa);
|
|
633
633
|
if (!allowWork)
|
|
634
634
|
continue;
|
|
@@ -698,7 +698,7 @@ class CanvasParticles {
|
|
|
698
698
|
}
|
|
699
699
|
/** Clear the canvas and render the particles and their connections onto the canvas */
|
|
700
700
|
#render() {
|
|
701
|
-
this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
|
|
701
|
+
this.ctx.clearRect(0, 0, this.canvas.rect.width, this.canvas.rect.height);
|
|
702
702
|
this.ctx.globalAlpha = this.color.alpha;
|
|
703
703
|
this.ctx.fillStyle = this.color.hex;
|
|
704
704
|
this.ctx.strokeStyle = this.color.hex;
|
|
@@ -738,7 +738,7 @@ class CanvasParticles {
|
|
|
738
738
|
this.enableAnimating = false;
|
|
739
739
|
this.isAnimating = false;
|
|
740
740
|
if (clear !== false)
|
|
741
|
-
this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
|
|
741
|
+
this.ctx.clearRect(0, 0, this.canvas.rect.width, this.canvas.rect.height);
|
|
742
742
|
return true;
|
|
743
743
|
}
|
|
744
744
|
/** Gracefully destroy the instance and remove the canvas element */
|
package/dist/index.umd.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
!function(t,i){"object"==typeof exports&&"undefined"!=typeof module?module.exports=i():"function"==typeof define&&define.amd?define(i):(t="undefined"!=typeof globalThis?globalThis:t||self).CanvasParticles=i()}(this,function(){"use strict";function t(t,i,e,s){if(null==i)return e;const{min:n=-1/0,max:a=1/0}=s??{};return i<n?console.warn(`option.${t} was clamped to ${n} as ${i} is too low`):i>a&&console.warn(`option.${t} was clamped to ${a} as ${i} is too high`),function(t,i){return isNaN(+t)?i:+t}(Math.min(Math.max(i??e,n),a),e)}const i=2*Math.PI;const e=function(t){let i=t>>>0;return{next(){let t=i+1831565813|0;return i=t,t=Math.imul(t^t>>>15,1|t),t^=t+Math.imul(t^t>>>7,61|t),((t^t>>>14)>>>0)/4294967296}}}(4294967296*Math.random()).next;class CanvasParticles{static version="4.5.3";static MAX_DT=1e3/30;static BASE_DT=1e3/60;static interactionType=Object.freeze({NONE:0,SHIFT:1,MOVE:2});static generationType=Object.freeze({OFF:0,NEW:1,MATCH:2});static canvasIntersectionObserver=new IntersectionObserver(t=>{for(const i of t){const t=i.target,e=t.instance;if(!e.options?.animation)return;(t.inViewbox=i.isIntersecting)?e.option.animation?.startOnEnter&&e.start({auto:!0}):e.option.animation?.stopOnLeave&&e.stop({auto:!0,clear:!1})}},{rootMargin:"-1px"});static canvasResizeObserver=new ResizeObserver(t=>{for(const i of t){i.target.instance.updateCanvasRect()}const i=window.devicePixelRatio||1;for(const e of t){e.target.instance.#t(i)}});static instances=new Set;canvas;ctx;enableAnimating=!1;isAnimating=!1;lastAnimationFrame=0;particles=[];hasManualParticles=!1;clientX=1/0;clientY=1/0;mouseX=1/0;mouseY=1/0;dpr=1;width;height;offX;offY;option;color;constructor(t,i={}){let e;if(t instanceof HTMLCanvasElement)e=t;else{if("string"!=typeof t)throw new TypeError("selector is not a string and neither a HTMLCanvasElement itself");if(e=document.querySelector(t),!(e instanceof HTMLCanvasElement))throw new Error("selector does not point to a canvas")}this.canvas=e,this.canvas.instance=this,this.canvas.inViewbox=!0;const s=this.canvas.getContext("2d");if(!s)throw new Error("failed to get 2D context from canvas");this.ctx=s,this.options=i,CanvasParticles.instances.add(this),CanvasParticles.canvasIntersectionObserver.observe(this.canvas),CanvasParticles.canvasResizeObserver.observe(this.canvas)}updateCanvasRect(){const{top:t,left:i,width:e,height:s}=this.canvas.getBoundingClientRect();this.canvas.rect={top:t,left:i,width:e,height:s}}handleMouseMove(t){this.enableAnimating&&(this.clientX=t.clientX,this.clientY=t.clientY,this.isAnimating&&this.updateMousePos())}handleScroll(){this.enableAnimating&&(this.updateCanvasRect(),this.isAnimating&&this.updateMousePos())}updateMousePos(){const{top:t,left:i}=this.canvas.rect;this.mouseX=this.clientX-i,this.mouseY=this.clientY-t}#t(t=window.devicePixelRatio||1){const i=this.canvas.width=this.canvas.rect.width*t,e=this.canvas.height=this.canvas.rect.height*t;1!==t&&this.ctx.scale(t,t),this.mouseX=1/0,this.mouseY=1/0,this.width=Math.max(i+2*this.option.particles.connectDist,1),this.height=Math.max(e+2*this.option.particles.connectDist,1),this.offX=(i-this.width)/2,this.offY=(e-this.height)/2;const s=this.option.particles.generationType;s!==CanvasParticles.generationType.OFF&&(s===CanvasParticles.generationType.NEW||0===this.particles.length?this.newParticles():s===CanvasParticles.generationType.MATCH&&this.matchParticleCount({updateBounds:!0})),this.isAnimating&&this.#i()}resizeCanvas(t=!0){t&&this.updateCanvasRect(),this.#t()}#e(){let t=Math.round(this.option.particles.ppm*this.width*this.height/1e6);if(t=Math.min(this.option.particles.max,t),!isFinite(t))throw new RangeError("particleCount must be finite");return 0|t}newParticles({keepAuto:t=!1,keepManual:i=!0}={}){const e=this.#e();if(this.hasManualParticles&&(t||i)?(this.particles=this.particles.filter(e=>t&&!e.isManual||i&&e.isManual),this.hasManualParticles=this.particles.length>0):this.particles=[],!t)for(let t=0;t<e;t++)this.#s()}matchParticleCount({updateBounds:t=!1}={}){const i=this.#e();if(this.hasManualParticles){const t=[];let e=0;for(const s of this.particles)s.isManual?t.push(s):e<i&&(t.push(s),e++);this.particles=t}else this.particles=this.particles.slice(0,i);if(t)for(const t of this.particles)this.#n(t);for(let t=this.particles.length;t<i;t++)this.#s()}#s(){const t=e()*this.width,s=e()*this.height;this.createParticle(t,s,e()*i,(.5+.5*e())*this.option.particles.relSpeed,(.5+2*Math.pow(e(),5))*this.option.particles.relSize,!1)}createParticle(t,i,e,s,n,a=!0){const o={posX:t,posY:i,x:t,y:i,velX:0,velY:0,offX:0,offY:0,dir:e,speed:s,size:n,gridPos:{x:1,y:1},isVisible:!1,isManual:a,bounds:{top:-n,right:this.canvas.width+n,bottom:this.canvas.height+n,left:-n}};this.particles.push(o),this.hasManualParticles=!0}#a(t){t.bounds.top=-t.size,t.bounds.right=this.canvas.width+t.size,t.bounds.bottom=this.canvas.height+t.size,t.bounds.left=-t.size}#n(t){t.bounds.right=this.canvas.width+t.size,t.bounds.bottom=this.canvas.height+t.size}updateParticles(){const t=this.option.particles.relSpeed,i=this.option.particles.relSize;for(const s of this.particles)s.speed=(.5+.5*e())*t,s.size=(.5+2*Math.pow(e(),5))*i,this.#a(s)}#o(t){const i=this.option.gravity.repulsive>0,e=this.option.gravity.pulling>0;if(!i&&!e)return;const s=this.particles,n=s.length,a=this.option.particles.connectDist,o=a*this.option.gravity.repulsive*t,r=a*this.option.gravity.pulling*t,c=(a/2)**2,l=a**2/256;for(let t=0;t<n;t++){const i=s[t];for(let a=t+1;a<n;a++){const t=s[a],n=i.posX-t.posX,h=i.posY-t.posY,p=n*n+h*h;if(p>=c&&!e)continue;const d=1/Math.sqrt(p+l),u=d*d*d;if(p<c){const e=u*o,s=-n*e,a=-h*e;i.velX-=s,i.velY-=a,t.velX+=s,t.velY+=a}if(!e)continue;const f=u*r,g=-n*f,v=-h*f;i.velX+=g,i.velY+=v,t.velX-=g,t.velY-=v}}}#r(t){const e=this.width,s=this.height,n=this.offX,a=this.offY,o=this.mouseX,r=this.mouseY,c=this.option.mouse.interactionType===CanvasParticles.interactionType.NONE,l=this.option.mouse.interactionType===CanvasParticles.interactionType.MOVE,h=this.option.mouse.connectDist,p=this.option.mouse.distRatio,d=this.option.particles.rotationSpeed*t,u=this.option.gravity.friction,f=this.option.gravity.maxVelocity,g=1-Math.pow(3/4,t);for(const v of this.particles){v.dir+=2*(Math.random()-.5)*d*t,v.dir%=i;const m=Math.sin(v.dir)*v.speed,x=Math.cos(v.dir)*v.speed;f>0&&(v.velX>f&&(v.velX=f),v.velX<-f&&(v.velX=-f),v.velY>f&&(v.velY=f),v.velY<-f&&(v.velY=-f)),v.posX+=(m+v.velX)*t,v.posY+=(x+v.velY)*t,v.posX%=e,v.posX<0&&(v.posX+=e),v.posY%=s,v.posY<0&&(v.posY+=s),v.velX*=Math.pow(u,t),v.velY*=Math.pow(u,t);const y=v.posX+n-o,b=v.posY+a-r;if(!c){const t=h/Math.hypot(y,b);p<t?(v.offX+=(t*y-y-v.offX)*g,v.offY+=(t*b-b-v.offY)*g):(v.offX-=v.offX*g,v.offY-=v.offY*g)}v.x=v.posX+v.offX,v.y=v.posY+v.offY,l&&(v.posX=v.x,v.posY=v.y),v.x+=n,v.y+=a,v.gridPos.x=+(v.x>=v.bounds.left)+ +(v.x>v.bounds.right),v.gridPos.y=+(v.y>=v.bounds.top)+ +(v.y>v.bounds.bottom),v.isVisible=1===v.gridPos.x&&1===v.gridPos.y}}#c(){const t=this.ctx;for(const e of this.particles)e.isVisible&&(e.size>1?(t.beginPath(),t.arc(e.x,e.y,e.size,0,i),t.fill(),t.closePath()):t.fillRect(e.x-e.size,e.y-e.size,2*e.size,2*e.size))}#l(t,i){const e=this.particles,s=e.length,n=new Map;for(let a=0;a<s;a++){const s=e[a],o=(s.x*i|0)+Math.imul(s.y*i,t),r=n.get(o);r?r.push(a):n.set(o,[a])}return n}static#h(t,i){return!(!t.isVisible&&!i.isVisible)||!(t.gridPos.x===i.gridPos.x&&1!==t.gridPos.x||t.gridPos.y===i.gridPos.y&&1!==t.gridPos.y)}#p(){const t=this.particles,i=t.length,e=this.ctx,s=this.option.particles.connectDist,n=s**2,a=(s/2)**2,o=1/s,r=Math.ceil(this.width*o),c=s>=Math.min(this.canvas.width,this.canvas.height),l=n*this.option.particles.maxWork,h=this.color.alpha,p=this.color.alpha*s,d=[],u=this.#l(r,o);let f=0,g=!0;function v(t,i,s,o){const r=t-s,c=i-o,u=r*r+c*c;u>n||(u>a?(e.globalAlpha=p/Math.sqrt(u)-h,e.beginPath(),e.moveTo(t,i),e.lineTo(s,o),e.stroke()):d.push(t,i,s,o),f+=u,g=f<l)}function m(i,e,s){for(const n of i){if(e>=n)continue;const i=t[n];if((c||CanvasParticles.#h(s,i))&&(v(s.x,s.y,i.x,i.y),!g))break}}function x(i,e){for(const s of i){const i=t[s];if((c||CanvasParticles.#h(e,i))&&(v(e.x,e.y,i.x,i.y),!g))break}}for(let e=0;e<i;e++){f=0,g=!0;let s,n=t[e],a=n.x*o|0,c=n.y*o|0,l=a+Math.imul(c,r);if((s=u.get(l+1))&&x(s,n),g&&((s=u.get(l+r))&&x(s,n),g&&((s=u.get(l+r+1))&&x(s,n),g&&((s=u.get(l+r-1))&&x(s,n),g)))){if(a>=0&&c>=0&&a<r-2&&(s=u.get(l))&&m(s||[],e,n),++e>=i)break;if(f=0,g=!0,n=t[e],a=n.x*o|0,c=n.y*o|0,l=a+Math.imul(c,r),(s=u.get(l+r+1))&&x(s,n),g&&((s=u.get(l+r-1))&&x(s,n),g&&((s=u.get(l+1))&&x(s,n),g&&((s=u.get(l+r))&&x(s,n),g)))){if(a>=0&&c>=0&&a<r-2&&(s=u.get(l))&&m(s||[],e,n),++e>=i)break;f=0,g=!0,n=t[e],a=n.x*o|0,c=n.y*o|0,l=a+Math.imul(c,r),(s=u.get(l+r))&&x(s,n),g&&((s=u.get(l+1))&&x(s,n),g&&(a>=0&&c>=0&&a<r-2&&(s=u.get(l))&&m(s||[],e,n),g&&((s=u.get(l+r-1))&&x(s,n),g&&(s=u.get(l+r+1))&&x(s,n))))}}}if(d.length){e.globalAlpha=h,e.beginPath();for(let t=0;t<d.length;t+=4)e.moveTo(d[t],d[t+1]),e.lineTo(d[t+2],d[t+3]);e.stroke()}}#d(t){const i=this.ctx,{width:e,height:s}=this.canvas;i.save(),i.globalAlpha=.5,i.beginPath();for(let n=.5;n<=e;n+=t)i.moveTo(n,0),i.lineTo(n,s);for(let n=.5;n<=s;n+=t)i.moveTo(0,n),i.lineTo(e,n);i.stroke(),i.restore()}#u(){const t=this.ctx,i=this.particles,e=i.length;t.save(),t.globalAlpha=1,t.fillStyle="#fff",t.textAlign="center",t.textBaseline="middle";for(let s=0;s<e;s++){const e=i[s];t.fillText(String(s),e.x,e.y)}t.restore()}#f(){const t=performance.now(),i=Math.min(t-this.lastAnimationFrame,CanvasParticles.MAX_DT)/CanvasParticles.BASE_DT;this.#o(i),this.#r(i),this.lastAnimationFrame=t}#i(){this.ctx.clearRect(0,0,this.canvas.width,this.canvas.height),this.ctx.globalAlpha=this.color.alpha,this.ctx.fillStyle=this.color.hex,this.ctx.strokeStyle=this.color.hex,this.ctx.lineWidth=1,this.#c(),this.option.particles.drawLines&&this.#p(),this.option.debug.drawGrid&&this.#d(this.option.particles.connectDist),this.option.debug.drawIndexes&&this.#u()}#g(){this.isAnimating&&(requestAnimationFrame(()=>this.#g()),this.#f(),this.#i())}start({auto:t=!1}={}){return this.isAnimating||t&&!this.enableAnimating||(this.enableAnimating=!0,this.isAnimating=!0,this.updateCanvasRect(),requestAnimationFrame(()=>this.#g())),!this.canvas.inViewbox&&this.option.animation.startOnEnter&&(this.isAnimating=!1),this}stop({auto:t=!1,clear:i=!0}={}){return t||(this.enableAnimating=!1),this.isAnimating=!1,!1!==i&&this.ctx.clearRect(0,0,this.canvas.width,this.canvas.height),!0}destroy(){this.stop(),CanvasParticles.instances.delete(this),CanvasParticles.canvasIntersectionObserver.unobserve(this.canvas),CanvasParticles.canvasResizeObserver.unobserve(this.canvas),this.canvas?.remove(),Object.keys(this).forEach(t=>delete this[t])}set options(i){const e=t;this.option={background:i.background??!1,animation:{startOnEnter:!!(i.animation?.startOnEnter??1),stopOnLeave:!!(i.animation?.stopOnLeave??1)},mouse:{interactionType:~~e("mouse.interactionType",i.mouse?.interactionType,CanvasParticles.interactionType.MOVE,{min:0,max:2}),connectDist:1,distRatio:e("mouse.distRatio",i.mouse?.distRatio,2/3,{min:0})},particles:{generationType:~~e("particles.generationType",i.particles?.generationType,CanvasParticles.generationType.MATCH,{min:0,max:2}),drawLines:!!(i.particles?.drawLines??1),color:i.particles?.color??"black",ppm:~~e("particles.ppm",i.particles?.ppm,100),max:Math.round(e("particles.max",i.particles?.max,1/0,{min:0})),maxWork:Math.round(e("particles.maxWork",i.particles?.maxWork,1/0,{min:0})),connectDist:~~e("particles.connectDistance",i.particles?.connectDistance,150,{min:1}),relSpeed:e("particles.relSpeed",i.particles?.relSpeed,1,{min:0}),relSize:e("particles.relSize",i.particles?.relSize,1,{min:0}),rotationSpeed:e("particles.rotationSpeed",i.particles?.rotationSpeed,2,{min:0})/100},gravity:{repulsive:e("gravity.repulsive",i.gravity?.repulsive,0,{min:0}),pulling:e("gravity.pulling",i.gravity?.pulling,0,{min:0}),friction:e("gravity.friction",i.gravity?.friction,.8,{min:0,max:1}),maxVelocity:e("gravity.maxVelocity",i.gravity?.maxVelocity,1/0,{min:0})},debug:{drawGrid:!!i.debug?.drawGrid,drawIndexes:!!i.debug?.drawIndexes}},this.setBackground(this.option.background),this.setMouseConnectDistMult(i.mouse?.connectDistMult),this.setParticleColor(this.option.particles.color)}get options(){return this.option}setBackground(t){if(t){if("string"!=typeof t)throw new TypeError("background is not a string");this.canvas.style.background=this.option.background=t}}setMouseConnectDistMult(i){const e=t("mouse.connectDistMult",i,2/3,{min:0});this.option.mouse.connectDist=this.option.particles.connectDist*e}setParticleColor(t){if(this.ctx.fillStyle=t,"#"===String(this.ctx.fillStyle)[0])this.color={hex:String(this.ctx.fillStyle),alpha:1};else{let t=String(this.ctx.fillStyle).split(",").at(-1);t=t?.slice(1,-1)??"1",this.ctx.fillStyle=String(this.ctx.fillStyle).split(",").slice(0,-1).join(",")+", 1)",this.color={hex:String(this.ctx.fillStyle),alpha:isNaN(+t)?1:+t}}}}return window.addEventListener("mousemove",t=>{for(const i of CanvasParticles.instances)i.handleMouseMove(t)},{passive:!0}),window.addEventListener("scroll",()=>{for(const t of CanvasParticles.instances)t.handleScroll()},{passive:!0}),CanvasParticles});
|
|
1
|
+
!function(t,i){"object"==typeof exports&&"undefined"!=typeof module?module.exports=i():"function"==typeof define&&define.amd?define(i):(t="undefined"!=typeof globalThis?globalThis:t||self).CanvasParticles=i()}(this,function(){"use strict";function t(t,i,e,s){if(null==i)return e;const{min:a=-1/0,max:n=1/0}=s??{};return i<a?console.warn(`option.${t} was clamped to ${a} as ${i} is too low`):i>n&&console.warn(`option.${t} was clamped to ${n} as ${i} is too high`),function(t,i){return isNaN(+t)?i:+t}(Math.min(Math.max(i??e,a),n),e)}const i=2*Math.PI;const e=(s=4294967296*Math.random()|0,function(){let t=s+=1831565813;return t=Math.imul(t^t>>>15,1|t),t^=t+Math.imul(t^t>>>7,61|t),((t^t>>>14)>>>0)/4294967296});var s;class CanvasParticles{static version="4.5.5";static MAX_DT=1e3/30;static BASE_DT=1e3/60;static interactionType=Object.freeze({NONE:0,SHIFT:1,MOVE:2});static generationType=Object.freeze({OFF:0,NEW:1,MATCH:2});static canvasIntersectionObserver=new IntersectionObserver(t=>{for(const i of t){const t=i.target,e=t.instance;if(!e.options?.animation)return;(t.inViewbox=i.isIntersecting)?e.option.animation?.startOnEnter&&e.start({auto:!0}):e.option.animation?.stopOnLeave&&e.stop({auto:!0,clear:!1})}},{rootMargin:"-1px"});static canvasResizeObserver=new ResizeObserver(t=>{for(const i of t){i.target.instance.updateCanvasRect()}const i=window.devicePixelRatio||1;for(const e of t){e.target.instance.#t(i)}});static instances=new Set;canvas;ctx;enableAnimating=!1;isAnimating=!1;lastAnimationFrame=0;particles=[];hasManualParticles=!1;clientX=1/0;clientY=1/0;mouseX=1/0;mouseY=1/0;dpr=1;width;height;offX;offY;option;color;constructor(t,i={}){let e;if(t instanceof HTMLCanvasElement)e=t;else{if("string"!=typeof t)throw new TypeError("selector is not a string and neither a HTMLCanvasElement itself");if(e=document.querySelector(t),!(e instanceof HTMLCanvasElement))throw new Error("selector does not point to a canvas")}this.canvas=e,this.canvas.instance=this,this.canvas.inViewbox=!0;const s=this.canvas.getContext("2d");if(!s)throw new Error("failed to get 2D context from canvas");this.ctx=s,this.options=i,CanvasParticles.instances.add(this),CanvasParticles.canvasIntersectionObserver.observe(this.canvas),CanvasParticles.canvasResizeObserver.observe(this.canvas)}updateCanvasRect(){const{top:t,left:i,width:e,height:s}=this.canvas.getBoundingClientRect();this.canvas.rect={top:t,left:i,width:e,height:s}}handleMouseMove(t){this.enableAnimating&&(this.clientX=t.clientX,this.clientY=t.clientY,this.isAnimating&&this.updateMousePos())}handleScroll(){this.enableAnimating&&(this.updateCanvasRect(),this.isAnimating&&this.updateMousePos())}updateMousePos(){const{top:t,left:i}=this.canvas.rect;this.mouseX=this.clientX-i,this.mouseY=this.clientY-t}#t(t=window.devicePixelRatio||1){t<1&&(t=1);const i=this.canvas.width=this.canvas.rect.width*t,e=this.canvas.height=this.canvas.rect.height*t;t>1&&this.ctx.scale(t,t),this.mouseX=1/0,this.mouseY=1/0,this.width=Math.max(i+2*this.option.particles.connectDist,1),this.height=Math.max(e+2*this.option.particles.connectDist,1),this.offX=(i-this.width)/2,this.offY=(e-this.height)/2;const s=this.option.particles.generationType;s!==CanvasParticles.generationType.OFF&&(s===CanvasParticles.generationType.NEW||0===this.particles.length?this.newParticles():s===CanvasParticles.generationType.MATCH&&this.matchParticleCount({updateBounds:!0})),this.isAnimating&&this.#i()}resizeCanvas(t=!0){t&&this.updateCanvasRect(),this.#t()}#e(){let t=Math.round(this.option.particles.ppm*this.width*this.height/1e6);if(t=Math.min(this.option.particles.max,t),!isFinite(t))throw new RangeError("particleCount must be finite");return 0|t}newParticles({keepAuto:t=!1,keepManual:i=!0}={}){const e=this.#e();if(this.hasManualParticles&&(t||i)?(this.particles=this.particles.filter(e=>t&&!e.isManual||i&&e.isManual),this.hasManualParticles=this.particles.length>0):this.particles=[],!t)for(let t=0;t<e;t++)this.#s()}matchParticleCount({updateBounds:t=!1}={}){const i=this.#e();if(this.hasManualParticles){const t=[];let e=0;for(const s of this.particles)s.isManual?t.push(s):e<i&&(t.push(s),e++);this.particles=t}else this.particles=this.particles.slice(0,i);if(t)for(const t of this.particles)this.#a(t);for(let t=this.particles.length;t<i;t++)this.#s()}#s(){const t=e()*this.width,s=e()*this.height;this.createParticle(t,s,e()*i,(.5+.5*e())*this.option.particles.relSpeed,(.5+2*Math.pow(e(),5))*this.option.particles.relSize,!1)}createParticle(t,i,e,s,a,n=!0){const o={posX:t,posY:i,x:t,y:i,velX:0,velY:0,offX:0,offY:0,dir:e,speed:s,size:a,gridPos:{x:1,y:1},isVisible:!1,isManual:n,bounds:{top:-a,right:this.canvas.width+a,bottom:this.canvas.height+a,left:-a}};this.particles.push(o),this.hasManualParticles=!0}#n(t){t.bounds.top=-t.size,t.bounds.right=this.canvas.width+t.size,t.bounds.bottom=this.canvas.height+t.size,t.bounds.left=-t.size}#a(t){t.bounds.right=this.canvas.width+t.size,t.bounds.bottom=this.canvas.height+t.size}updateParticles(){const t=this.option.particles.relSpeed,i=this.option.particles.relSize;for(const s of this.particles)s.speed=(.5+.5*e())*t,s.size=(.5+2*Math.pow(e(),5))*i,this.#n(s)}#o(t){const i=this.option.gravity.repulsive>0,e=this.option.gravity.pulling>0;if(!i&&!e)return;const s=this.particles,a=s.length,n=this.option.particles.connectDist,o=n*this.option.gravity.repulsive*t,r=n*this.option.gravity.pulling*t,c=(n/2)**2,l=n**2/256;for(let t=0;t<a;t++){const i=s[t];for(let n=t+1;n<a;n++){const t=s[n],a=i.posX-t.posX,h=i.posY-t.posY,p=a*a+h*h;if(p>=c&&!e)continue;const d=1/Math.sqrt(p+l),u=d*d*d;if(p<c){const e=u*o,s=-a*e,n=-h*e;i.velX-=s,i.velY-=n,t.velX+=s,t.velY+=n}if(!e)continue;const g=u*r,v=-a*g,f=-h*g;i.velX+=v,i.velY+=f,t.velX-=v,t.velY-=f}}}#r(t){const e=this.width,s=this.height,a=this.offX,n=this.offY,o=this.mouseX,r=this.mouseY,c=this.option.mouse.interactionType===CanvasParticles.interactionType.NONE,l=this.option.mouse.interactionType===CanvasParticles.interactionType.MOVE,h=this.option.mouse.connectDist,p=this.option.mouse.distRatio,d=this.option.particles.rotationSpeed*t,u=this.option.gravity.friction,g=this.option.gravity.maxVelocity,v=1-Math.pow(3/4,t);for(const f of this.particles){f.dir+=2*(Math.random()-.5)*d*t,f.dir%=i;const m=Math.sin(f.dir)*f.speed,x=Math.cos(f.dir)*f.speed;g>0&&(f.velX>g&&(f.velX=g),f.velX<-g&&(f.velX=-g),f.velY>g&&(f.velY=g),f.velY<-g&&(f.velY=-g)),f.posX+=(m+f.velX)*t,f.posY+=(x+f.velY)*t,f.posX%=e,f.posX<0&&(f.posX+=e),f.posY%=s,f.posY<0&&(f.posY+=s),f.velX*=Math.pow(u,t),f.velY*=Math.pow(u,t);const y=f.posX+a-o,b=f.posY+n-r;if(!c){const t=h/Math.hypot(y,b);p<t?(f.offX+=(t*y-y-f.offX)*v,f.offY+=(t*b-b-f.offY)*v):(f.offX-=f.offX*v,f.offY-=f.offY*v)}f.x=f.posX+f.offX,f.y=f.posY+f.offY,l&&(f.posX=f.x,f.posY=f.y),f.x+=a,f.y+=n,f.gridPos.x=+(f.x>=f.bounds.left)+ +(f.x>f.bounds.right),f.gridPos.y=+(f.y>=f.bounds.top)+ +(f.y>f.bounds.bottom),f.isVisible=1===f.gridPos.x&&1===f.gridPos.y}}#c(){const t=this.ctx,e=window.devicePixelRatio||1;for(const s of this.particles)s.isVisible&&(s.size>1/e?(t.beginPath(),t.arc(s.x,s.y,s.size,0,i),t.fill(),t.closePath()):t.fillRect(s.x-s.size,s.y-s.size,2*s.size,2*s.size))}#l(t,i){const e=this.particles,s=e.length,a=new Map;for(let n=0;n<s;n++){const s=e[n],o=(s.x*i|0)+Math.imul(s.y*i,t),r=a.get(o);r?r.push(n):a.set(o,[n])}return a}static#h(t,i){return!(!t.isVisible&&!i.isVisible)||!(t.gridPos.x===i.gridPos.x&&1!==t.gridPos.x||t.gridPos.y===i.gridPos.y&&1!==t.gridPos.y)}#p(){const t=this.particles,i=t.length,e=this.ctx,s=this.option.particles.connectDist,a=s**2,n=(s/2)**2,o=1/s,r=Math.ceil(this.width*o),c=Math.ceil(this.height*o),l=s>=Math.min(this.canvas.width,this.canvas.height),h=a*this.option.particles.maxWork,p=this.color.alpha,d=this.color.alpha*s,u=[],g=this.#l(r,o);let v=0,f=!0;function m(t,i,s,o){const r=t-s,c=i-o,l=r*r+c*c;l>a||(l>n?(e.globalAlpha=d/Math.sqrt(l)-p,e.beginPath(),e.moveTo(t,i),e.lineTo(s,o),e.stroke()):u.push(t,i,s,o),v+=l,f=v<h)}function x(i,e,s){for(const a of i){if(e>=a)continue;const i=t[a];if((l||CanvasParticles.#h(s,i))&&(m(s.x,s.y,i.x,i.y),!f))break}}function y(i,e){for(const s of i){const i=t[s];if((l||CanvasParticles.#h(e,i))&&(m(e.x,e.y,i.x,i.y),!f))break}}for(let e=0;e<i;e++){v=0,f=!0;let s,a=t[e],n=a.x*o|0,l=a.y*o|0,h=n+Math.imul(l,r);if((s=g.get(h+1))&&y(s,a),f&&((s=g.get(h+r))&&y(s,a),f&&((s=g.get(h+r+1))&&y(s,a),f&&((s=g.get(h+r-1))&&y(s,a),f&&n>=0&&l>=0&&n<r-2&&l<c-2&&(s=g.get(h))&&x(s||[],e,a)))),++e>=i)break;if(v=0,f=!0,a=t[e],n=a.x*o|0,l=a.y*o|0,h=n+Math.imul(l,r),(s=g.get(h+r+1))&&y(s,a),f&&((s=g.get(h+r-1))&&y(s,a),f&&((s=g.get(h+1))&&y(s,a),f&&((s=g.get(h+r))&&y(s,a),f&&n>=0&&l>=0&&n<r-2&&l<c-2&&(s=g.get(h))&&x(s||[],e,a)))),++e>=i)break;v=0,f=!0,a=t[e],n=a.x*o|0,l=a.y*o|0,h=n+Math.imul(l,r),(s=g.get(h+r))&&y(s,a),f&&((s=g.get(h+1))&&y(s,a),f&&(n>=0&&l>=0&&n<r-2&&l<c-2&&(s=g.get(h))&&x(s||[],e,a),f&&((s=g.get(h+r-1))&&y(s,a),f&&(s=g.get(h+r+1))&&y(s,a))))}if(u.length){e.globalAlpha=p,e.beginPath();for(let t=0;t<u.length;t+=4)e.moveTo(u[t],u[t+1]),e.lineTo(u[t+2],u[t+3]);e.stroke()}}#d(t){const i=this.ctx,{width:e,height:s}=this.canvas;i.save(),i.globalAlpha=.5,i.beginPath();for(let a=.5;a<=e;a+=t)i.moveTo(a,0),i.lineTo(a,s);for(let a=.5;a<=s;a+=t)i.moveTo(0,a),i.lineTo(e,a);i.stroke(),i.restore()}#u(){const t=this.ctx,i=this.particles,e=i.length;t.save(),t.globalAlpha=1,t.fillStyle="#fff",t.textAlign="center",t.textBaseline="middle";for(let s=0;s<e;s++){const e=i[s];t.fillText(String(s),e.x,e.y)}t.restore()}#g(){const t=performance.now(),i=Math.min(t-this.lastAnimationFrame,CanvasParticles.MAX_DT)/CanvasParticles.BASE_DT;this.#o(i),this.#r(i),this.lastAnimationFrame=t}#i(){this.ctx.clearRect(0,0,this.canvas.rect.width,this.canvas.rect.height),this.ctx.globalAlpha=this.color.alpha,this.ctx.fillStyle=this.color.hex,this.ctx.strokeStyle=this.color.hex,this.ctx.lineWidth=1,this.#c(),this.option.particles.drawLines&&this.#p(),this.option.debug.drawGrid&&this.#d(this.option.particles.connectDist),this.option.debug.drawIndexes&&this.#u()}#v(){this.isAnimating&&(requestAnimationFrame(()=>this.#v()),this.#g(),this.#i())}start({auto:t=!1}={}){return this.isAnimating||t&&!this.enableAnimating||(this.enableAnimating=!0,this.isAnimating=!0,this.updateCanvasRect(),requestAnimationFrame(()=>this.#v())),!this.canvas.inViewbox&&this.option.animation.startOnEnter&&(this.isAnimating=!1),this}stop({auto:t=!1,clear:i=!0}={}){return t||(this.enableAnimating=!1),this.isAnimating=!1,!1!==i&&this.ctx.clearRect(0,0,this.canvas.rect.width,this.canvas.rect.height),!0}destroy(){this.stop(),CanvasParticles.instances.delete(this),CanvasParticles.canvasIntersectionObserver.unobserve(this.canvas),CanvasParticles.canvasResizeObserver.unobserve(this.canvas),this.canvas?.remove(),Object.keys(this).forEach(t=>delete this[t])}set options(i){const e=t;this.option={background:i.background??!1,animation:{startOnEnter:!!(i.animation?.startOnEnter??1),stopOnLeave:!!(i.animation?.stopOnLeave??1)},mouse:{interactionType:~~e("mouse.interactionType",i.mouse?.interactionType,CanvasParticles.interactionType.MOVE,{min:0,max:2}),connectDist:1,distRatio:e("mouse.distRatio",i.mouse?.distRatio,2/3,{min:0})},particles:{generationType:~~e("particles.generationType",i.particles?.generationType,CanvasParticles.generationType.MATCH,{min:0,max:2}),drawLines:!!(i.particles?.drawLines??1),color:i.particles?.color??"black",ppm:~~e("particles.ppm",i.particles?.ppm,100),max:Math.round(e("particles.max",i.particles?.max,1/0,{min:0})),maxWork:Math.round(e("particles.maxWork",i.particles?.maxWork,1/0,{min:0})),connectDist:~~e("particles.connectDistance",i.particles?.connectDistance,150,{min:1}),relSpeed:e("particles.relSpeed",i.particles?.relSpeed,1,{min:0}),relSize:e("particles.relSize",i.particles?.relSize,1,{min:0}),rotationSpeed:e("particles.rotationSpeed",i.particles?.rotationSpeed,2,{min:0})/100},gravity:{repulsive:e("gravity.repulsive",i.gravity?.repulsive,0,{min:0}),pulling:e("gravity.pulling",i.gravity?.pulling,0,{min:0}),friction:e("gravity.friction",i.gravity?.friction,.8,{min:0,max:1}),maxVelocity:e("gravity.maxVelocity",i.gravity?.maxVelocity,1/0,{min:0})},debug:{drawGrid:!!i.debug?.drawGrid,drawIndexes:!!i.debug?.drawIndexes}},this.setBackground(this.option.background),this.setMouseConnectDistMult(i.mouse?.connectDistMult),this.setParticleColor(this.option.particles.color)}get options(){return this.option}setBackground(t){if(t){if("string"!=typeof t)throw new TypeError("background is not a string");this.canvas.style.background=this.option.background=t}}setMouseConnectDistMult(i){const e=t("mouse.connectDistMult",i,2/3,{min:0});this.option.mouse.connectDist=this.option.particles.connectDist*e}setParticleColor(t){if(this.ctx.fillStyle=t,"#"===String(this.ctx.fillStyle)[0])this.color={hex:String(this.ctx.fillStyle),alpha:1};else{let t=String(this.ctx.fillStyle).split(",").at(-1);t=t?.slice(1,-1)??"1",this.ctx.fillStyle=String(this.ctx.fillStyle).split(",").slice(0,-1).join(",")+", 1)",this.color={hex:String(this.ctx.fillStyle),alpha:isNaN(+t)?1:+t}}}}return window.addEventListener("mousemove",t=>{for(const i of CanvasParticles.instances)i.handleMouseMove(t)},{passive:!0}),window.addEventListener("scroll",()=>{for(const t of CanvasParticles.instances)t.handleScroll()},{passive:!0}),CanvasParticles});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "canvasparticles-js",
|
|
3
|
-
"version": "4.5.
|
|
3
|
+
"version": "4.5.5",
|
|
4
4
|
"description": "In an HTML canvas, a bunch of interactive particles connected with lines when they approach each other.",
|
|
5
5
|
"author": "Khoeckman",
|
|
6
6
|
"license": "MIT",
|
|
@@ -29,12 +29,12 @@
|
|
|
29
29
|
"@rollup/plugin-replace": "^6.0.3",
|
|
30
30
|
"@rollup/plugin-terser": "^1.0.0",
|
|
31
31
|
"@rollup/plugin-typescript": "^12.3.0",
|
|
32
|
-
"@types/node": "^25.
|
|
32
|
+
"@types/node": "^25.5.2",
|
|
33
33
|
"prettier": "^3.8.1",
|
|
34
|
-
"rollup": "^4.
|
|
34
|
+
"rollup": "^4.60.1",
|
|
35
35
|
"rollup-plugin-delete": "^3.0.2",
|
|
36
36
|
"tslib": "^2.8.1",
|
|
37
|
-
"typescript": "^
|
|
37
|
+
"typescript": "^6.0.2"
|
|
38
38
|
},
|
|
39
39
|
"publishConfig": {
|
|
40
40
|
"access": "public"
|
package/src/index.ts
CHANGED
|
@@ -9,24 +9,19 @@ import type { CanvasParticlesOptions, CanvasParticlesOptionsInput } from './type
|
|
|
9
9
|
const TWO_PI = 2 * Math.PI
|
|
10
10
|
|
|
11
11
|
/** Extremely fast, simple 32‑bit PRNG */
|
|
12
|
-
function
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
state = t
|
|
19
|
-
t = Math.imul(t ^ (t >>> 15), t | 1)
|
|
20
|
-
t ^= t + Math.imul(t ^ (t >>> 7), t | 61)
|
|
21
|
-
return ((t ^ (t >>> 14)) >>> 0) / 4294967296
|
|
22
|
-
},
|
|
12
|
+
function mulberry32(seed: number) {
|
|
13
|
+
return function () {
|
|
14
|
+
let t = (seed += 0x6d2b79f5)
|
|
15
|
+
t = Math.imul(t ^ (t >>> 15), t | 1)
|
|
16
|
+
t ^= t + Math.imul(t ^ (t >>> 7), t | 61)
|
|
17
|
+
return ((t ^ (t >>> 14)) >>> 0) / 4294967296
|
|
23
18
|
}
|
|
24
19
|
}
|
|
25
20
|
|
|
26
|
-
// Mulberry32 is
|
|
21
|
+
// Mulberry32 is x4 faster than Math.random()
|
|
27
22
|
// Benchmark: https://jsbm.dev/muLCWR9RJCbmy
|
|
28
23
|
// Spectral test: /demo/mulberry32.html
|
|
29
|
-
const prng =
|
|
24
|
+
const prng = mulberry32((Math.random() * 4294967296) | 0)
|
|
30
25
|
|
|
31
26
|
// Injected by Rollup
|
|
32
27
|
declare const __VERSION__: string
|
|
@@ -178,11 +173,13 @@ export default class CanvasParticles {
|
|
|
178
173
|
|
|
179
174
|
/** Resize the canvas and update particles accordingly */
|
|
180
175
|
#resizeCanvas(dpr = window.devicePixelRatio || 1) {
|
|
176
|
+
if (dpr < 1) dpr = 1
|
|
177
|
+
|
|
181
178
|
const width = (this.canvas.width = this.canvas.rect.width * dpr)
|
|
182
179
|
const height = (this.canvas.height = this.canvas.rect.height * dpr)
|
|
183
180
|
|
|
184
181
|
// Must be set every time width or height changes because scale is removed
|
|
185
|
-
if (dpr
|
|
182
|
+
if (dpr > 1) this.ctx.scale(dpr, dpr)
|
|
186
183
|
|
|
187
184
|
// Hide the mouse when resizing because it must be outside the viewport to do so
|
|
188
185
|
this.mouseX = Infinity
|
|
@@ -500,12 +497,13 @@ export default class CanvasParticles {
|
|
|
500
497
|
/** Draw the particles on the canvas */
|
|
501
498
|
#renderParticles() {
|
|
502
499
|
const ctx = this.ctx
|
|
500
|
+
const dpr = window.devicePixelRatio || 1
|
|
503
501
|
|
|
504
502
|
for (const p of this.particles) {
|
|
505
503
|
if (!p.isVisible) continue
|
|
506
504
|
|
|
507
505
|
// Draw particles smaller than 1px as a square instead of a circle for performance
|
|
508
|
-
if (p.size > 1) {
|
|
506
|
+
if (p.size > 1 / dpr) {
|
|
509
507
|
// Draw circle
|
|
510
508
|
ctx.beginPath()
|
|
511
509
|
ctx.arc(p.x, p.y, p.size, 0, TWO_PI)
|
|
@@ -558,6 +556,7 @@ export default class CanvasParticles {
|
|
|
558
556
|
|
|
559
557
|
const invCellSize = 1 / maxDist
|
|
560
558
|
const stride = Math.ceil(this.width * invCellSize)
|
|
559
|
+
const rows = Math.ceil(this.height * invCellSize)
|
|
561
560
|
|
|
562
561
|
const drawAll = maxDist >= Math.min(this.canvas.width, this.canvas.height)
|
|
563
562
|
const maxWorkPerParticle = maxDistSq * this.option.particles.maxWork
|
|
@@ -643,15 +642,19 @@ export default class CanvasParticles {
|
|
|
643
642
|
let cell
|
|
644
643
|
|
|
645
644
|
if ((cell = grid.get(key + 1))) renderConnectionsToCell(cell, pa) // (+1, 0)
|
|
646
|
-
if (
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
645
|
+
if (allowWork) {
|
|
646
|
+
if ((cell = grid.get(key + stride))) renderConnectionsToCell(cell, pa) // (0, +1)
|
|
647
|
+
if (allowWork) {
|
|
648
|
+
if ((cell = grid.get(key + stride + 1))) renderConnectionsToCell(cell, pa) // (+1, +1)
|
|
649
|
+
if (allowWork) {
|
|
650
|
+
if ((cell = grid.get(key + stride - 1))) renderConnectionsToCell(cell, pa) // (-1, +1)
|
|
651
|
+
if (allowWork) {
|
|
652
|
+
if (cellX >= 0 && cellY >= 0 && cellX < stride - 2 && cellY < rows - 2 && (cell = grid.get(key)))
|
|
653
|
+
renderConnectionsToOwnCell(cell || [], a, pa)
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
}
|
|
655
658
|
|
|
656
659
|
// Next iteration
|
|
657
660
|
if (++a >= len) break
|
|
@@ -666,15 +669,19 @@ export default class CanvasParticles {
|
|
|
666
669
|
key = cellX + Math.imul(cellY, stride)
|
|
667
670
|
|
|
668
671
|
if ((cell = grid.get(key + stride + 1))) renderConnectionsToCell(cell, pa) // (+1, +1)
|
|
669
|
-
if (
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
672
|
+
if (allowWork) {
|
|
673
|
+
if ((cell = grid.get(key + stride - 1))) renderConnectionsToCell(cell, pa) // (-1, +1)
|
|
674
|
+
if (allowWork) {
|
|
675
|
+
if ((cell = grid.get(key + 1))) renderConnectionsToCell(cell, pa) // (+1, 0)
|
|
676
|
+
if (allowWork) {
|
|
677
|
+
if ((cell = grid.get(key + stride))) renderConnectionsToCell(cell, pa) // (0, +1)
|
|
678
|
+
if (allowWork) {
|
|
679
|
+
if (cellX >= 0 && cellY >= 0 && cellX < stride - 2 && cellY < rows - 2 && (cell = grid.get(key)))
|
|
680
|
+
renderConnectionsToOwnCell(cell || [], a, pa)
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
}
|
|
684
|
+
}
|
|
678
685
|
|
|
679
686
|
// Next iteration
|
|
680
687
|
if (++a >= len) break
|
|
@@ -692,7 +699,7 @@ export default class CanvasParticles {
|
|
|
692
699
|
if (!allowWork) continue
|
|
693
700
|
if ((cell = grid.get(key + 1))) renderConnectionsToCell(cell, pa) // (+1, 0)
|
|
694
701
|
if (!allowWork) continue
|
|
695
|
-
if (cellX >= 0 && cellY >= 0 && cellX < stride - 2 && (cell = grid.get(key)))
|
|
702
|
+
if (cellX >= 0 && cellY >= 0 && cellX < stride - 2 && cellY < rows - 2 && (cell = grid.get(key)))
|
|
696
703
|
renderConnectionsToOwnCell(cell || [], a, pa)
|
|
697
704
|
if (!allowWork) continue
|
|
698
705
|
if ((cell = grid.get(key + stride - 1))) renderConnectionsToCell(cell, pa) // (-1, +1)
|
|
@@ -774,7 +781,7 @@ export default class CanvasParticles {
|
|
|
774
781
|
|
|
775
782
|
/** Clear the canvas and render the particles and their connections onto the canvas */
|
|
776
783
|
#render() {
|
|
777
|
-
this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height)
|
|
784
|
+
this.ctx.clearRect(0, 0, this.canvas.rect.width, this.canvas.rect.height)
|
|
778
785
|
|
|
779
786
|
this.ctx.globalAlpha = this.color.alpha
|
|
780
787
|
this.ctx.fillStyle = this.color.hex
|
|
@@ -816,7 +823,7 @@ export default class CanvasParticles {
|
|
|
816
823
|
stop({ auto = false, clear = true }: { auto?: boolean; clear?: boolean } = {}): boolean {
|
|
817
824
|
if (!auto) this.enableAnimating = false
|
|
818
825
|
this.isAnimating = false
|
|
819
|
-
if (clear !== false) this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height)
|
|
826
|
+
if (clear !== false) this.ctx.clearRect(0, 0, this.canvas.rect.width, this.canvas.rect.height)
|
|
820
827
|
return true
|
|
821
828
|
}
|
|
822
829
|
|