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 +12 -12
- package/dist/index.mjs +12 -12
- package/dist/index.umd.js +1 -1
- package/package.json +1 -1
- package/src/index.ts +11 -11
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.
|
|
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
|
|
245
|
-
const eps =
|
|
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
|
|
408
|
+
const maxDistSq = maxDist ** 2;
|
|
409
409
|
const halfMaxDist = maxDist / 2;
|
|
410
|
-
const halfMaxDistSq = 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
|
-
|
|
574
|
-
|
|
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.
|
|
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
|
|
243
|
-
const eps =
|
|
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
|
|
406
|
+
const maxDistSq = maxDist ** 2;
|
|
407
407
|
const halfMaxDist = maxDist / 2;
|
|
408
|
-
const halfMaxDistSq = 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
|
-
|
|
572
|
-
|
|
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
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
|
|
289
|
-
const eps =
|
|
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
|
|
477
|
+
const maxDistSq = maxDist ** 2
|
|
478
478
|
const halfMaxDist = maxDist / 2
|
|
479
|
-
const halfMaxDistSq = 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
|
-
|
|
673
|
-
|
|
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 */
|