canvasparticles-js 4.2.1 → 4.2.2

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,7 +21,7 @@ function Mulberry32(seed) {
21
21
  // Spectral test: /demo/mulberry32.html
22
22
  const prng = Mulberry32(Math.random() * 2 ** 32).next;
23
23
  class CanvasParticles {
24
- static version = "4.2.1";
24
+ static version = "4.2.2";
25
25
  static MAX_DT = 1000 / 50; // milliseconds between updates @ 50 FPS
26
26
  static BASE_DT = 1000 / 60; // milliseconds between updates @ 60 FPS
27
27
  /** Defines mouse interaction types with the particles */
@@ -241,8 +241,8 @@ class CanvasParticles {
241
241
  const gravRepulsiveMult = connectDist * this.option.gravity.repulsive * step;
242
242
  const gravPullingMult = connectDist * this.option.gravity.pulling * step;
243
243
  const maxRepulsiveDist = connectDist / 2;
244
- const maxRepulsiveDistSq = maxRepulsiveDist * maxRepulsiveDist;
245
- const eps = (connectDist * connectDist) / 256;
244
+ const maxRepulsiveDistSq = maxRepulsiveDist ** 2;
245
+ const eps = connectDist ** 2 / 256;
246
246
  for (let i = 0; i < len; i++) {
247
247
  const particleA = particles[i];
248
248
  for (let j = i + 1; j < len; j++) {
@@ -405,9 +405,9 @@ class CanvasParticles {
405
405
  const particles = this.particles;
406
406
  const ctx = this.ctx;
407
407
  const maxDist = this.option.particles.connectDist;
408
- const maxDistSq = maxDist * maxDist;
408
+ const maxDistSq = maxDist ** 2;
409
409
  const halfMaxDist = maxDist / 2;
410
- const halfMaxDistSq = halfMaxDist * halfMaxDist;
410
+ const halfMaxDistSq = halfMaxDist ** 2;
411
411
  const drawAll = maxDist >= Math.min(this.canvas.width, this.canvas.height);
412
412
  const maxWorkPerParticle = maxDistSq * this.option.particles.maxWork;
413
413
  const alpha = this.color.alpha;
@@ -531,16 +531,16 @@ class CanvasParticles {
531
531
  },
532
532
  mouse: {
533
533
  interactionType: ~~pno('mouse.interactionType', options.mouse?.interactionType, CanvasParticles.interactionType.MOVE, { min: 0, max: 2 }),
534
- connectDistMult: pno('mouse.connectDistMult', options.mouse?.connectDistMult, 2 / 3),
534
+ connectDistMult: pno('mouse.connectDistMult', options.mouse?.connectDistMult, 2 / 3, { min: 0 }),
535
535
  connectDist: 1 /* post processed */,
536
- distRatio: pno('mouse.distRatio', options.mouse?.distRatio, 2 / 3),
536
+ distRatio: pno('mouse.distRatio', options.mouse?.distRatio, 2 / 3, { min: 0 }),
537
537
  },
538
538
  particles: {
539
539
  regenerateOnResize: !!options.particles?.regenerateOnResize,
540
540
  drawLines: !!(options.particles?.drawLines ?? true),
541
541
  color: options.particles?.color ?? 'black',
542
542
  ppm: ~~pno('particles.ppm', options.particles?.ppm, 100),
543
- max: Math.round(pno('particles.max', options.particles?.max, Infinity)),
543
+ max: Math.round(pno('particles.max', options.particles?.max, Infinity, { min: 0 })),
544
544
  maxWork: Math.round(pno('particles.maxWork', options.particles?.maxWork, Infinity, { min: 0 })),
545
545
  connectDist: ~~pno('particles.connectDistance', options.particles?.connectDistance, 150, { min: 1 }),
546
546
  relSpeed: pno('particles.relSpeed', options.particles?.relSpeed, 1, { min: 0 }),
@@ -548,8 +548,8 @@ class CanvasParticles {
548
548
  rotationSpeed: pno('particles.rotationSpeed', options.particles?.rotationSpeed, 2, { min: 0 }) / 100,
549
549
  },
550
550
  gravity: {
551
- repulsive: pno('gravity.repulsive', options.gravity?.repulsive, 0),
552
- pulling: pno('gravity.pulling', options.gravity?.pulling, 0),
551
+ repulsive: pno('gravity.repulsive', options.gravity?.repulsive, 0, { min: 0 }),
552
+ pulling: pno('gravity.pulling', options.gravity?.pulling, 0, { min: 0 }),
553
553
  friction: pno('gravity.friction', options.gravity?.friction, 0.8, { min: 0, max: 1 }),
554
554
  },
555
555
  };
@@ -570,8 +570,8 @@ class CanvasParticles {
570
570
  }
571
571
  /** @public Transform the distance multiplier (float) to absolute distance (px) */
572
572
  setMouseConnectDistMult(connectDistMult) {
573
- this.option.mouse.connectDist =
574
- this.option.particles.connectDist * (isNaN(connectDistMult) ? 2 / 3 : connectDistMult);
573
+ const mult = CanvasParticles.parseNumericOption('mouse.connectDistMult', connectDistMult, 2 / 3, { min: 0 });
574
+ this.option.mouse.connectDist = this.option.particles.connectDist * mult;
575
575
  }
576
576
  /** @public Format particle color and opacity */
577
577
  setParticleColor(color) {
package/dist/index.mjs CHANGED
@@ -19,7 +19,7 @@ function Mulberry32(seed) {
19
19
  // Spectral test: /demo/mulberry32.html
20
20
  const prng = Mulberry32(Math.random() * 2 ** 32).next;
21
21
  class CanvasParticles {
22
- static version = "4.2.1";
22
+ static version = "4.2.2";
23
23
  static MAX_DT = 1000 / 50; // milliseconds between updates @ 50 FPS
24
24
  static BASE_DT = 1000 / 60; // milliseconds between updates @ 60 FPS
25
25
  /** Defines mouse interaction types with the particles */
@@ -239,8 +239,8 @@ class CanvasParticles {
239
239
  const gravRepulsiveMult = connectDist * this.option.gravity.repulsive * step;
240
240
  const gravPullingMult = connectDist * this.option.gravity.pulling * step;
241
241
  const maxRepulsiveDist = connectDist / 2;
242
- const maxRepulsiveDistSq = maxRepulsiveDist * maxRepulsiveDist;
243
- const eps = (connectDist * connectDist) / 256;
242
+ const maxRepulsiveDistSq = maxRepulsiveDist ** 2;
243
+ const eps = connectDist ** 2 / 256;
244
244
  for (let i = 0; i < len; i++) {
245
245
  const particleA = particles[i];
246
246
  for (let j = i + 1; j < len; j++) {
@@ -403,9 +403,9 @@ class CanvasParticles {
403
403
  const particles = this.particles;
404
404
  const ctx = this.ctx;
405
405
  const maxDist = this.option.particles.connectDist;
406
- const maxDistSq = maxDist * maxDist;
406
+ const maxDistSq = maxDist ** 2;
407
407
  const halfMaxDist = maxDist / 2;
408
- const halfMaxDistSq = halfMaxDist * halfMaxDist;
408
+ const halfMaxDistSq = halfMaxDist ** 2;
409
409
  const drawAll = maxDist >= Math.min(this.canvas.width, this.canvas.height);
410
410
  const maxWorkPerParticle = maxDistSq * this.option.particles.maxWork;
411
411
  const alpha = this.color.alpha;
@@ -529,16 +529,16 @@ class CanvasParticles {
529
529
  },
530
530
  mouse: {
531
531
  interactionType: ~~pno('mouse.interactionType', options.mouse?.interactionType, CanvasParticles.interactionType.MOVE, { min: 0, max: 2 }),
532
- connectDistMult: pno('mouse.connectDistMult', options.mouse?.connectDistMult, 2 / 3),
532
+ connectDistMult: pno('mouse.connectDistMult', options.mouse?.connectDistMult, 2 / 3, { min: 0 }),
533
533
  connectDist: 1 /* post processed */,
534
- distRatio: pno('mouse.distRatio', options.mouse?.distRatio, 2 / 3),
534
+ distRatio: pno('mouse.distRatio', options.mouse?.distRatio, 2 / 3, { min: 0 }),
535
535
  },
536
536
  particles: {
537
537
  regenerateOnResize: !!options.particles?.regenerateOnResize,
538
538
  drawLines: !!(options.particles?.drawLines ?? true),
539
539
  color: options.particles?.color ?? 'black',
540
540
  ppm: ~~pno('particles.ppm', options.particles?.ppm, 100),
541
- max: Math.round(pno('particles.max', options.particles?.max, Infinity)),
541
+ max: Math.round(pno('particles.max', options.particles?.max, Infinity, { min: 0 })),
542
542
  maxWork: Math.round(pno('particles.maxWork', options.particles?.maxWork, Infinity, { min: 0 })),
543
543
  connectDist: ~~pno('particles.connectDistance', options.particles?.connectDistance, 150, { min: 1 }),
544
544
  relSpeed: pno('particles.relSpeed', options.particles?.relSpeed, 1, { min: 0 }),
@@ -546,8 +546,8 @@ class CanvasParticles {
546
546
  rotationSpeed: pno('particles.rotationSpeed', options.particles?.rotationSpeed, 2, { min: 0 }) / 100,
547
547
  },
548
548
  gravity: {
549
- repulsive: pno('gravity.repulsive', options.gravity?.repulsive, 0),
550
- pulling: pno('gravity.pulling', options.gravity?.pulling, 0),
549
+ repulsive: pno('gravity.repulsive', options.gravity?.repulsive, 0, { min: 0 }),
550
+ pulling: pno('gravity.pulling', options.gravity?.pulling, 0, { min: 0 }),
551
551
  friction: pno('gravity.friction', options.gravity?.friction, 0.8, { min: 0, max: 1 }),
552
552
  },
553
553
  };
@@ -568,8 +568,8 @@ class CanvasParticles {
568
568
  }
569
569
  /** @public Transform the distance multiplier (float) to absolute distance (px) */
570
570
  setMouseConnectDistMult(connectDistMult) {
571
- this.option.mouse.connectDist =
572
- this.option.particles.connectDist * (isNaN(connectDistMult) ? 2 / 3 : connectDistMult);
571
+ const mult = CanvasParticles.parseNumericOption('mouse.connectDistMult', connectDistMult, 2 / 3, { min: 0 });
572
+ this.option.mouse.connectDist = this.option.particles.connectDist * mult;
573
573
  }
574
574
  /** @public Format particle color and opacity */
575
575
  setParticleColor(color) {
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";const t=2*Math.PI;const i=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}}}(Math.random()*2**32).next;class e{static version="4.2.1";static MAX_DT=20;static BASE_DT=1e3/60;static interactionType=Object.freeze({NONE:0,SHIFT:1,MOVE:2});static canvasIntersectionObserver=new IntersectionObserver(t=>{for(let i=0;i<t.length;i++){const e=t[i],s=e.target,n=s.instance;if(!n.options?.animation)return;(s.inViewbox=e.isIntersecting)?n.options.animation?.startOnEnter&&n.start({auto:!0}):n.options.animation?.stopOnLeave&&n.stop({auto:!0,clear:!1})}});static canvasResizeObserver=new ResizeObserver(t=>{for(let i=0;i<t.length;i++){t[i].target.instance.updateCanvasRect()}for(let i=0;i<t.length;i++){t[i].target.instance.resizeCanvas()}});static defaultIfNaN=(t,i)=>isNaN(+t)?i:+t;static parseNumericOption=(t,i,s,n)=>{if(null==i)return s;const{min:o=-1/0,max:a=1/0}=n??{};return isFinite(o)&&i<o?console.warn(new RangeError(`option.${t} was clamped to ${o} as ${i} is too low`)):isFinite(a)&&i>a&&console.warn(new RangeError(`option.${t} was clamped to ${a} as ${i} is too high`)),e.defaultIfNaN(Math.min(Math.max(i??s,o),a),s)};canvas;ctx;enableAnimating=!1;isAnimating=!1;lastAnimationFrame=0;particles=[];particleCount=0;clientX=1/0;clientY=1/0;mouseX=1/0;mouseY=1/0;width;height;offX;offY;option;color;constructor(t,i={}){let s;if(t instanceof HTMLCanvasElement)s=t;else{if("string"!=typeof t)throw new TypeError("selector is not a string and neither a HTMLCanvasElement itself");if(s=document.querySelector(t),!(s instanceof HTMLCanvasElement))throw new Error("selector does not point to a canvas")}this.canvas=s,this.canvas.instance=this,this.canvas.inViewbox=!0;const n=this.canvas.getContext("2d");if(!n)throw new Error("failed to get 2D context from canvas");this.ctx=n,this.options=i,e.canvasIntersectionObserver.observe(this.canvas),e.canvasResizeObserver.observe(this.canvas),this.resizeCanvas=this.resizeCanvas.bind(this),this.handleMouseMove=this.handleMouseMove.bind(this),this.handleScroll=this.handleScroll.bind(this),this.updateCanvasRect(),this.resizeCanvas(),window.addEventListener("mousemove",this.handleMouseMove,{passive:!0}),window.addEventListener("scroll",this.handleScroll,{passive:!0})}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}resizeCanvas(){const t=this.canvas.width=this.canvas.rect.width,i=this.canvas.height=this.canvas.rect.height;this.mouseX=1/0,this.mouseY=1/0,this.width=Math.max(t+2*this.option.particles.connectDist,1),this.height=Math.max(i+2*this.option.particles.connectDist,1),this.offX=(t-this.width)/2,this.offY=(i-this.height)/2,this.option.particles.regenerateOnResize||0===this.particles.length?this.newParticles():this.matchParticleCount({updateBounds:!0}),this.isAnimating&&this.#t()}#i(){const t=this.option.particles.ppm*this.width*this.height/1e6|0;if(this.particleCount=Math.min(this.option.particles.max,t),!isFinite(this.particleCount))throw new RangeError("particleCount must be finite")}newParticles(){this.#i(),this.particles=[];for(let t=0;t<this.particleCount;t++)this.createParticle()}matchParticleCount({updateBounds:t=!1}={}){for(this.#i(),this.particles=this.particles.slice(0,this.particleCount),t&&this.particles.forEach(t=>this.#e(t));this.particleCount>this.particles.length;)this.createParticle()}createParticle(e,s,n,o,a){const r={posX:e="number"==typeof e?e-this.offX:i()*this.width,posY:s="number"==typeof s?s-this.offY:i()*this.height,x:e,y:s,velX:0,velY:0,offX:0,offY:0,dir:n||i()*t,speed:o||(.5+.5*i())*this.option.particles.relSpeed,size:a||(.5+i()**5*2)*this.option.particles.relSize,gridPos:{x:1,y:1},isVisible:!1};this.#e(r),this.particles.push(r)}#e(t){t.bounds={top:-t.size,right:this.canvas.width+t.size,bottom:this.canvas.height+t.size,left:-t.size}}#s(t){const i=this.option.gravity.repulsive>0,e=this.option.gravity.pulling>0;if(!i&&!e)return;const s=this.particleCount,n=this.particles,o=this.option.particles.connectDist,a=o*this.option.gravity.repulsive*t,r=o*this.option.gravity.pulling*t,c=o/2,h=c*c,l=o*o/256;for(let t=0;t<s;t++){const i=n[t];for(let o=t+1;o<s;o++){const t=n[o],s=i.posX-t.posX,c=i.posY-t.posY,p=s*s+c*c;if(p>=h&&!e)continue;let u,d,f;u=Math.atan2(-c,-s),d=1/(p+l);const m=Math.cos(u),v=Math.sin(u);if(p<h){f=d*a;const e=m*f,s=v*f;i.velX-=e,i.velY-=s,t.velX+=e,t.velY+=s}if(!e)continue;f=d*r;const g=m*f,y=v*f;i.velX+=g,i.velY+=y,t.velX-=g,t.velY-=y}}}#n(i){const s=this.particleCount,n=this.particles,o=this.width,a=this.height,r=this.offX,c=this.offY,h=this.mouseX,l=this.mouseY,p=this.option.particles.rotationSpeed*i,u=this.option.gravity.friction,d=this.option.mouse.connectDist,f=this.option.mouse.distRatio,m=this.option.mouse.interactionType===e.interactionType.NONE,v=this.option.mouse.interactionType===e.interactionType.MOVE,g=1-Math.pow(.75,i);for(let e=0;e<s;e++){const s=n[e];s.dir+=2*(Math.random()-.5)*p*i,s.dir%=t;const y=Math.sin(s.dir)*s.speed,x=Math.cos(s.dir)*s.speed;s.posX+=(y+s.velX)*i,s.posY+=(x+s.velY)*i,s.posX%=o,s.posX<0&&(s.posX+=o),s.posY%=a,s.posY<0&&(s.posY+=a),s.velX*=Math.pow(u,i),s.velY*=Math.pow(u,i);const b=s.posX+r-h,w=s.posY+c-l;if(!m){const t=d/Math.hypot(b,w);f<t?(s.offX+=(t*b-b-s.offX)*g,s.offY+=(t*w-w-s.offY)*g):(s.offX-=s.offX*g,s.offY-=s.offY*g)}s.x=s.posX+s.offX,s.y=s.posY+s.offY,v&&(s.posX=s.x,s.posY=s.y),s.x+=r,s.y+=c,this.#o(s),s.isVisible=1===s.gridPos.x&&1===s.gridPos.y}}#o(t){t.gridPos.x=+(t.x>=t.bounds.left)+ +(t.x>t.bounds.right),t.gridPos.y=+(t.y>=t.bounds.top)+ +(t.y>t.bounds.bottom)}#a(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)}#r(){const i=this.particleCount,e=this.particles,s=this.ctx;for(let n=0;n<i;n++){const i=e[n];i.isVisible&&(i.size>1?(s.beginPath(),s.arc(i.x,i.y,i.size,0,t),s.fill(),s.closePath()):s.fillRect(i.x-i.size,i.y-i.size,2*i.size,2*i.size))}}#c(){const t=this.particleCount,i=this.particles,e=this.ctx,s=this.option.particles.connectDist,n=s*s,o=s/2,a=o*o,r=s>=Math.min(this.canvas.width,this.canvas.height),c=n*this.option.particles.maxWork,h=this.color.alpha,l=this.color.alpha*s,p=[];for(let s=0;s<t;s++){const o=i[s];let u=0;for(let d=s+1;d<t;d++){const t=i[d];if(!r&&!this.#a(o,t))continue;const s=o.x-t.x,f=o.y-t.y,m=s*s+f*f;if(!(m>n)&&(m>a?(e.globalAlpha=l/Math.sqrt(m)-h,e.beginPath(),e.moveTo(o.x,o.y),e.lineTo(t.x,t.y),e.stroke()):p.push([o.x,o.y,t.x,t.y]),(u+=m)>=c))break}}if(p.length){e.globalAlpha=h,e.beginPath();for(let t=0;t<p.length;t++){const i=p[t];e.moveTo(i[0],i[1]),e.lineTo(i[2],i[3])}e.stroke()}}#t(){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.#r(),this.options.particles.drawLines&&this.#c()}#h(){if(!this.isAnimating)return;requestAnimationFrame(()=>this.#h());const t=performance.now(),i=Math.min(t-this.lastAnimationFrame,e.MAX_DT)/e.BASE_DT;this.#s(i),this.#n(i),this.#t(),this.lastAnimationFrame=t}start({auto:t=!1}={}){return this.isAnimating||t&&!this.enableAnimating||(this.enableAnimating=!0,this.isAnimating=!0,this.updateCanvasRect(),requestAnimationFrame(()=>this.#h())),!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(),e.canvasIntersectionObserver.unobserve(this.canvas),e.canvasResizeObserver.unobserve(this.canvas),window.removeEventListener("mousemove",this.handleMouseMove),window.removeEventListener("scroll",this.handleScroll),this.canvas?.remove(),Object.keys(this).forEach(t=>delete this[t])}set options(t){const i=e.parseNumericOption;this.option={background:t.background??!1,animation:{startOnEnter:!!(t.animation?.startOnEnter??1),stopOnLeave:!!(t.animation?.stopOnLeave??1)},mouse:{interactionType:~~i("mouse.interactionType",t.mouse?.interactionType,e.interactionType.MOVE,{min:0,max:2}),connectDistMult:i("mouse.connectDistMult",t.mouse?.connectDistMult,2/3),connectDist:1,distRatio:i("mouse.distRatio",t.mouse?.distRatio,2/3)},particles:{regenerateOnResize:!!t.particles?.regenerateOnResize,drawLines:!!(t.particles?.drawLines??1),color:t.particles?.color??"black",ppm:~~i("particles.ppm",t.particles?.ppm,100),max:Math.round(i("particles.max",t.particles?.max,1/0)),maxWork:Math.round(i("particles.maxWork",t.particles?.maxWork,1/0,{min:0})),connectDist:~~i("particles.connectDistance",t.particles?.connectDistance,150,{min:1}),relSpeed:i("particles.relSpeed",t.particles?.relSpeed,1,{min:0}),relSize:i("particles.relSize",t.particles?.relSize,1,{min:0}),rotationSpeed:i("particles.rotationSpeed",t.particles?.rotationSpeed,2,{min:0})/100},gravity:{repulsive:i("gravity.repulsive",t.gravity?.repulsive,0),pulling:i("gravity.pulling",t.gravity?.pulling,0),friction:i("gravity.friction",t.gravity?.friction,.8,{min:0,max:1})}},this.setBackground(this.option.background),this.setMouseConnectDistMult(this.option.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(t){this.option.mouse.connectDist=this.option.particles.connectDist*(isNaN(t)?2/3:t)}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 e});
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";const t=2*Math.PI;const i=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}}}(Math.random()*2**32).next;class e{static version="4.2.2";static MAX_DT=20;static BASE_DT=1e3/60;static interactionType=Object.freeze({NONE:0,SHIFT:1,MOVE:2});static canvasIntersectionObserver=new IntersectionObserver(t=>{for(let i=0;i<t.length;i++){const e=t[i],s=e.target,n=s.instance;if(!n.options?.animation)return;(s.inViewbox=e.isIntersecting)?n.options.animation?.startOnEnter&&n.start({auto:!0}):n.options.animation?.stopOnLeave&&n.stop({auto:!0,clear:!1})}});static canvasResizeObserver=new ResizeObserver(t=>{for(let i=0;i<t.length;i++){t[i].target.instance.updateCanvasRect()}for(let i=0;i<t.length;i++){t[i].target.instance.resizeCanvas()}});static defaultIfNaN=(t,i)=>isNaN(+t)?i:+t;static parseNumericOption=(t,i,s,n)=>{if(null==i)return s;const{min:o=-1/0,max:a=1/0}=n??{};return isFinite(o)&&i<o?console.warn(new RangeError(`option.${t} was clamped to ${o} as ${i} is too low`)):isFinite(a)&&i>a&&console.warn(new RangeError(`option.${t} was clamped to ${a} as ${i} is too high`)),e.defaultIfNaN(Math.min(Math.max(i??s,o),a),s)};canvas;ctx;enableAnimating=!1;isAnimating=!1;lastAnimationFrame=0;particles=[];particleCount=0;clientX=1/0;clientY=1/0;mouseX=1/0;mouseY=1/0;width;height;offX;offY;option;color;constructor(t,i={}){let s;if(t instanceof HTMLCanvasElement)s=t;else{if("string"!=typeof t)throw new TypeError("selector is not a string and neither a HTMLCanvasElement itself");if(s=document.querySelector(t),!(s instanceof HTMLCanvasElement))throw new Error("selector does not point to a canvas")}this.canvas=s,this.canvas.instance=this,this.canvas.inViewbox=!0;const n=this.canvas.getContext("2d");if(!n)throw new Error("failed to get 2D context from canvas");this.ctx=n,this.options=i,e.canvasIntersectionObserver.observe(this.canvas),e.canvasResizeObserver.observe(this.canvas),this.resizeCanvas=this.resizeCanvas.bind(this),this.handleMouseMove=this.handleMouseMove.bind(this),this.handleScroll=this.handleScroll.bind(this),this.updateCanvasRect(),this.resizeCanvas(),window.addEventListener("mousemove",this.handleMouseMove,{passive:!0}),window.addEventListener("scroll",this.handleScroll,{passive:!0})}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}resizeCanvas(){const t=this.canvas.width=this.canvas.rect.width,i=this.canvas.height=this.canvas.rect.height;this.mouseX=1/0,this.mouseY=1/0,this.width=Math.max(t+2*this.option.particles.connectDist,1),this.height=Math.max(i+2*this.option.particles.connectDist,1),this.offX=(t-this.width)/2,this.offY=(i-this.height)/2,this.option.particles.regenerateOnResize||0===this.particles.length?this.newParticles():this.matchParticleCount({updateBounds:!0}),this.isAnimating&&this.#t()}#i(){const t=this.option.particles.ppm*this.width*this.height/1e6|0;if(this.particleCount=Math.min(this.option.particles.max,t),!isFinite(this.particleCount))throw new RangeError("particleCount must be finite")}newParticles(){this.#i(),this.particles=[];for(let t=0;t<this.particleCount;t++)this.createParticle()}matchParticleCount({updateBounds:t=!1}={}){for(this.#i(),this.particles=this.particles.slice(0,this.particleCount),t&&this.particles.forEach(t=>this.#e(t));this.particleCount>this.particles.length;)this.createParticle()}createParticle(e,s,n,o,a){const r={posX:e="number"==typeof e?e-this.offX:i()*this.width,posY:s="number"==typeof s?s-this.offY:i()*this.height,x:e,y:s,velX:0,velY:0,offX:0,offY:0,dir:n||i()*t,speed:o||(.5+.5*i())*this.option.particles.relSpeed,size:a||(.5+i()**5*2)*this.option.particles.relSize,gridPos:{x:1,y:1},isVisible:!1};this.#e(r),this.particles.push(r)}#e(t){t.bounds={top:-t.size,right:this.canvas.width+t.size,bottom:this.canvas.height+t.size,left:-t.size}}#s(t){const i=this.option.gravity.repulsive>0,e=this.option.gravity.pulling>0;if(!i&&!e)return;const s=this.particleCount,n=this.particles,o=this.option.particles.connectDist,a=o*this.option.gravity.repulsive*t,r=o*this.option.gravity.pulling*t,c=(o/2)**2,h=o**2/256;for(let t=0;t<s;t++){const i=n[t];for(let o=t+1;o<s;o++){const t=n[o],s=i.posX-t.posX,l=i.posY-t.posY,p=s*s+l*l;if(p>=c&&!e)continue;let u,d,m;u=Math.atan2(-l,-s),d=1/(p+h);const f=Math.cos(u),v=Math.sin(u);if(p<c){m=d*a;const e=f*m,s=v*m;i.velX-=e,i.velY-=s,t.velX+=e,t.velY+=s}if(!e)continue;m=d*r;const g=f*m,y=v*m;i.velX+=g,i.velY+=y,t.velX-=g,t.velY-=y}}}#n(i){const s=this.particleCount,n=this.particles,o=this.width,a=this.height,r=this.offX,c=this.offY,h=this.mouseX,l=this.mouseY,p=this.option.particles.rotationSpeed*i,u=this.option.gravity.friction,d=this.option.mouse.connectDist,m=this.option.mouse.distRatio,f=this.option.mouse.interactionType===e.interactionType.NONE,v=this.option.mouse.interactionType===e.interactionType.MOVE,g=1-Math.pow(.75,i);for(let e=0;e<s;e++){const s=n[e];s.dir+=2*(Math.random()-.5)*p*i,s.dir%=t;const y=Math.sin(s.dir)*s.speed,x=Math.cos(s.dir)*s.speed;s.posX+=(y+s.velX)*i,s.posY+=(x+s.velY)*i,s.posX%=o,s.posX<0&&(s.posX+=o),s.posY%=a,s.posY<0&&(s.posY+=a),s.velX*=Math.pow(u,i),s.velY*=Math.pow(u,i);const b=s.posX+r-h,M=s.posY+c-l;if(!f){const t=d/Math.hypot(b,M);m<t?(s.offX+=(t*b-b-s.offX)*g,s.offY+=(t*M-M-s.offY)*g):(s.offX-=s.offX*g,s.offY-=s.offY*g)}s.x=s.posX+s.offX,s.y=s.posY+s.offY,v&&(s.posX=s.x,s.posY=s.y),s.x+=r,s.y+=c,this.#o(s),s.isVisible=1===s.gridPos.x&&1===s.gridPos.y}}#o(t){t.gridPos.x=+(t.x>=t.bounds.left)+ +(t.x>t.bounds.right),t.gridPos.y=+(t.y>=t.bounds.top)+ +(t.y>t.bounds.bottom)}#a(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)}#r(){const i=this.particleCount,e=this.particles,s=this.ctx;for(let n=0;n<i;n++){const i=e[n];i.isVisible&&(i.size>1?(s.beginPath(),s.arc(i.x,i.y,i.size,0,t),s.fill(),s.closePath()):s.fillRect(i.x-i.size,i.y-i.size,2*i.size,2*i.size))}}#c(){const t=this.particleCount,i=this.particles,e=this.ctx,s=this.option.particles.connectDist,n=s**2,o=(s/2)**2,a=s>=Math.min(this.canvas.width,this.canvas.height),r=n*this.option.particles.maxWork,c=this.color.alpha,h=this.color.alpha*s,l=[];for(let s=0;s<t;s++){const p=i[s];let u=0;for(let d=s+1;d<t;d++){const t=i[d];if(!a&&!this.#a(p,t))continue;const s=p.x-t.x,m=p.y-t.y,f=s*s+m*m;if(!(f>n)&&(f>o?(e.globalAlpha=h/Math.sqrt(f)-c,e.beginPath(),e.moveTo(p.x,p.y),e.lineTo(t.x,t.y),e.stroke()):l.push([p.x,p.y,t.x,t.y]),(u+=f)>=r))break}}if(l.length){e.globalAlpha=c,e.beginPath();for(let t=0;t<l.length;t++){const i=l[t];e.moveTo(i[0],i[1]),e.lineTo(i[2],i[3])}e.stroke()}}#t(){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.#r(),this.options.particles.drawLines&&this.#c()}#h(){if(!this.isAnimating)return;requestAnimationFrame(()=>this.#h());const t=performance.now(),i=Math.min(t-this.lastAnimationFrame,e.MAX_DT)/e.BASE_DT;this.#s(i),this.#n(i),this.#t(),this.lastAnimationFrame=t}start({auto:t=!1}={}){return this.isAnimating||t&&!this.enableAnimating||(this.enableAnimating=!0,this.isAnimating=!0,this.updateCanvasRect(),requestAnimationFrame(()=>this.#h())),!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(),e.canvasIntersectionObserver.unobserve(this.canvas),e.canvasResizeObserver.unobserve(this.canvas),window.removeEventListener("mousemove",this.handleMouseMove),window.removeEventListener("scroll",this.handleScroll),this.canvas?.remove(),Object.keys(this).forEach(t=>delete this[t])}set options(t){const i=e.parseNumericOption;this.option={background:t.background??!1,animation:{startOnEnter:!!(t.animation?.startOnEnter??1),stopOnLeave:!!(t.animation?.stopOnLeave??1)},mouse:{interactionType:~~i("mouse.interactionType",t.mouse?.interactionType,e.interactionType.MOVE,{min:0,max:2}),connectDistMult:i("mouse.connectDistMult",t.mouse?.connectDistMult,2/3,{min:0}),connectDist:1,distRatio:i("mouse.distRatio",t.mouse?.distRatio,2/3,{min:0})},particles:{regenerateOnResize:!!t.particles?.regenerateOnResize,drawLines:!!(t.particles?.drawLines??1),color:t.particles?.color??"black",ppm:~~i("particles.ppm",t.particles?.ppm,100),max:Math.round(i("particles.max",t.particles?.max,1/0,{min:0})),maxWork:Math.round(i("particles.maxWork",t.particles?.maxWork,1/0,{min:0})),connectDist:~~i("particles.connectDistance",t.particles?.connectDistance,150,{min:1}),relSpeed:i("particles.relSpeed",t.particles?.relSpeed,1,{min:0}),relSize:i("particles.relSize",t.particles?.relSize,1,{min:0}),rotationSpeed:i("particles.rotationSpeed",t.particles?.rotationSpeed,2,{min:0})/100},gravity:{repulsive:i("gravity.repulsive",t.gravity?.repulsive,0,{min:0}),pulling:i("gravity.pulling",t.gravity?.pulling,0,{min:0}),friction:i("gravity.friction",t.gravity?.friction,.8,{min:0,max:1})}},this.setBackground(this.option.background),this.setMouseConnectDistMult(this.option.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(t){const i=e.parseNumericOption("mouse.connectDistMult",t,2/3,{min:0});this.option.mouse.connectDist=this.option.particles.connectDist*i}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 e});
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "canvasparticles-js",
3
- "version": "4.2.1",
3
+ "version": "4.2.2",
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",
package/src/index.ts CHANGED
@@ -285,8 +285,8 @@ export default class CanvasParticles {
285
285
  const gravRepulsiveMult = connectDist * this.option.gravity.repulsive * step
286
286
  const gravPullingMult = connectDist * this.option.gravity.pulling * step
287
287
  const maxRepulsiveDist = connectDist / 2
288
- const maxRepulsiveDistSq = maxRepulsiveDist * maxRepulsiveDist
289
- const eps = (connectDist * connectDist) / 256
288
+ const maxRepulsiveDistSq = maxRepulsiveDist ** 2
289
+ const eps = connectDist ** 2 / 256
290
290
 
291
291
  for (let i = 0; i < len; i++) {
292
292
  const particleA = particles[i]
@@ -474,9 +474,9 @@ export default class CanvasParticles {
474
474
  const particles = this.particles
475
475
  const ctx = this.ctx
476
476
  const maxDist = this.option.particles.connectDist
477
- const maxDistSq = maxDist * maxDist
477
+ const maxDistSq = maxDist ** 2
478
478
  const halfMaxDist = maxDist / 2
479
- const halfMaxDistSq = halfMaxDist * halfMaxDist
479
+ const halfMaxDistSq = halfMaxDist ** 2
480
480
  const drawAll = maxDist >= Math.min(this.canvas.width, this.canvas.height)
481
481
  const maxWorkPerParticle = maxDistSq * this.option.particles.maxWork
482
482
  const alpha = this.color.alpha
@@ -628,16 +628,16 @@ export default class CanvasParticles {
628
628
  CanvasParticles.interactionType.MOVE,
629
629
  { min: 0, max: 2 }
630
630
  ),
631
- connectDistMult: pno('mouse.connectDistMult', options.mouse?.connectDistMult, 2 / 3),
631
+ connectDistMult: pno('mouse.connectDistMult', options.mouse?.connectDistMult, 2 / 3, { min: 0 }),
632
632
  connectDist: 1 /* post processed */,
633
- distRatio: pno('mouse.distRatio', options.mouse?.distRatio, 2 / 3),
633
+ distRatio: pno('mouse.distRatio', options.mouse?.distRatio, 2 / 3, { min: 0 }),
634
634
  },
635
635
  particles: {
636
636
  regenerateOnResize: !!options.particles?.regenerateOnResize,
637
637
  drawLines: !!(options.particles?.drawLines ?? true),
638
638
  color: options.particles?.color ?? 'black',
639
639
  ppm: ~~pno('particles.ppm', options.particles?.ppm, 100),
640
- max: Math.round(pno('particles.max', options.particles?.max, Infinity)),
640
+ max: Math.round(pno('particles.max', options.particles?.max, Infinity, { min: 0 })),
641
641
  maxWork: Math.round(pno('particles.maxWork', options.particles?.maxWork, Infinity, { min: 0 })),
642
642
  connectDist: ~~pno('particles.connectDistance', options.particles?.connectDistance, 150, { min: 1 }),
643
643
  relSpeed: pno('particles.relSpeed', options.particles?.relSpeed, 1, { min: 0 }),
@@ -645,8 +645,8 @@ export default class CanvasParticles {
645
645
  rotationSpeed: pno('particles.rotationSpeed', options.particles?.rotationSpeed, 2, { min: 0 }) / 100,
646
646
  },
647
647
  gravity: {
648
- repulsive: pno('gravity.repulsive', options.gravity?.repulsive, 0),
649
- pulling: pno('gravity.pulling', options.gravity?.pulling, 0),
648
+ repulsive: pno('gravity.repulsive', options.gravity?.repulsive, 0, { min: 0 }),
649
+ pulling: pno('gravity.pulling', options.gravity?.pulling, 0, { min: 0 }),
650
650
  friction: pno('gravity.friction', options.gravity?.friction, 0.8, { min: 0, max: 1 }),
651
651
  },
652
652
  }
@@ -669,8 +669,8 @@ export default class CanvasParticles {
669
669
 
670
670
  /** @public Transform the distance multiplier (float) to absolute distance (px) */
671
671
  setMouseConnectDistMult(connectDistMult: number) {
672
- this.option.mouse.connectDist =
673
- this.option.particles.connectDist * (isNaN(connectDistMult) ? 2 / 3 : connectDistMult)
672
+ const mult = CanvasParticles.parseNumericOption('mouse.connectDistMult', connectDistMult, 2 / 3, { min: 0 })
673
+ this.option.mouse.connectDist = this.option.particles.connectDist * mult
674
674
  }
675
675
 
676
676
  /** @public Format particle color and opacity */