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 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 Mulberry32(seed) {
25
- let state = seed >>> 0;
26
- return {
27
- next() {
28
- let t = (state + 0x6d2b79f5) | 0;
29
- state = t;
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 ±392% faster than Math.random()
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 = Mulberry32(Math.random() * 4294967296).next;
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.3";
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 !== 1)
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 (!allowWork)
572
- continue;
573
- if ((cell = grid.get(key + stride)))
574
- renderConnectionsToCell(cell, pa); // (0, +1)
575
- if (!allowWork)
576
- continue;
577
- if ((cell = grid.get(key + stride + 1)))
578
- renderConnectionsToCell(cell, pa); // (+1, +1)
579
- if (!allowWork)
580
- continue;
581
- if ((cell = grid.get(key + stride - 1)))
582
- renderConnectionsToCell(cell, pa); // (-1, +1)
583
- if (!allowWork)
584
- continue;
585
- if (cellX >= 0 && cellY >= 0 && cellX < stride - 2 && (cell = grid.get(key)))
586
- renderConnectionsToOwnCell(cell || [], a, pa);
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 (!allowWork)
600
- continue;
601
- if ((cell = grid.get(key + stride - 1)))
602
- renderConnectionsToCell(cell, pa); // (-1, +1)
603
- if (!allowWork)
604
- continue;
605
- if ((cell = grid.get(key + 1)))
606
- renderConnectionsToCell(cell, pa); // (+1, 0)
607
- if (!allowWork)
608
- continue;
609
- if ((cell = grid.get(key + stride)))
610
- renderConnectionsToCell(cell, pa); // (0, +1)
611
- if (!allowWork)
612
- continue;
613
- if (cellX >= 0 && cellY >= 0 && cellX < stride - 2 && (cell = grid.get(key)))
614
- renderConnectionsToOwnCell(cell || [], a, pa);
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 Mulberry32(seed) {
23
- let state = seed >>> 0;
24
- return {
25
- next() {
26
- let t = (state + 0x6d2b79f5) | 0;
27
- state = t;
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 ±392% faster than Math.random()
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 = Mulberry32(Math.random() * 4294967296).next;
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.3";
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 !== 1)
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 (!allowWork)
570
- continue;
571
- if ((cell = grid.get(key + stride)))
572
- renderConnectionsToCell(cell, pa); // (0, +1)
573
- if (!allowWork)
574
- continue;
575
- if ((cell = grid.get(key + stride + 1)))
576
- renderConnectionsToCell(cell, pa); // (+1, +1)
577
- if (!allowWork)
578
- continue;
579
- if ((cell = grid.get(key + stride - 1)))
580
- renderConnectionsToCell(cell, pa); // (-1, +1)
581
- if (!allowWork)
582
- continue;
583
- if (cellX >= 0 && cellY >= 0 && cellX < stride - 2 && (cell = grid.get(key)))
584
- renderConnectionsToOwnCell(cell || [], a, pa);
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 (!allowWork)
598
- continue;
599
- if ((cell = grid.get(key + stride - 1)))
600
- renderConnectionsToCell(cell, pa); // (-1, +1)
601
- if (!allowWork)
602
- continue;
603
- if ((cell = grid.get(key + 1)))
604
- renderConnectionsToCell(cell, pa); // (+1, 0)
605
- if (!allowWork)
606
- continue;
607
- if ((cell = grid.get(key + stride)))
608
- renderConnectionsToCell(cell, pa); // (0, +1)
609
- if (!allowWork)
610
- continue;
611
- if (cellX >= 0 && cellY >= 0 && cellX < stride - 2 && (cell = grid.get(key)))
612
- renderConnectionsToOwnCell(cell || [], a, pa);
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",
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.3.5",
32
+ "@types/node": "^25.5.2",
33
33
  "prettier": "^3.8.1",
34
- "rollup": "^4.59.0",
34
+ "rollup": "^4.60.1",
35
35
  "rollup-plugin-delete": "^3.0.2",
36
36
  "tslib": "^2.8.1",
37
- "typescript": "^5.9.3"
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 Mulberry32(seed: number) {
13
- let state = seed >>> 0
14
-
15
- return {
16
- next() {
17
- let t = (state + 0x6d2b79f5) | 0
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 ±392% faster than Math.random()
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 = Mulberry32(Math.random() * 4294967296).next
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 !== 1) this.ctx.scale(dpr, 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 (!allowWork) continue
647
- if ((cell = grid.get(key + stride))) renderConnectionsToCell(cell, pa) // (0, +1)
648
- if (!allowWork) continue
649
- if ((cell = grid.get(key + stride + 1))) renderConnectionsToCell(cell, pa) // (+1, +1)
650
- if (!allowWork) continue
651
- if ((cell = grid.get(key + stride - 1))) renderConnectionsToCell(cell, pa) // (-1, +1)
652
- if (!allowWork) continue
653
- if (cellX >= 0 && cellY >= 0 && cellX < stride - 2 && (cell = grid.get(key)))
654
- renderConnectionsToOwnCell(cell || [], a, pa)
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 (!allowWork) continue
670
- if ((cell = grid.get(key + stride - 1))) renderConnectionsToCell(cell, pa) // (-1, +1)
671
- if (!allowWork) continue
672
- if ((cell = grid.get(key + 1))) renderConnectionsToCell(cell, pa) // (+1, 0)
673
- if (!allowWork) continue
674
- if ((cell = grid.get(key + stride))) renderConnectionsToCell(cell, pa) // (0, +1)
675
- if (!allowWork) continue
676
- if (cellX >= 0 && cellY >= 0 && cellX < stride - 2 && (cell = grid.get(key)))
677
- renderConnectionsToOwnCell(cell || [], a, pa)
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