canvasparticles-js 4.4.7 → 4.4.9
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 +9 -6
- package/dist/index.d.ts +2 -2
- package/dist/index.mjs +9 -6
- package/dist/index.umd.js +1 -1
- package/package.json +1 -1
- package/src/index.ts +7 -5
package/dist/index.cjs
CHANGED
|
@@ -22,7 +22,7 @@ function Mulberry32(seed) {
|
|
|
22
22
|
const prng = Mulberry32(Math.random() * 4294967296).next;
|
|
23
23
|
class CanvasParticles {
|
|
24
24
|
/** Version of the library, injected via Rollup replace plugin. */
|
|
25
|
-
static version = "4.4.
|
|
25
|
+
static version = "4.4.9";
|
|
26
26
|
static MAX_DT = 1000 / 30; // milliseconds between updates @ 30 FPS
|
|
27
27
|
static BASE_DT = 1000 / 60; // milliseconds between updates @ 60 FPS
|
|
28
28
|
/** Defines mouse interaction types with the particles */
|
|
@@ -162,8 +162,10 @@ class CanvasParticles {
|
|
|
162
162
|
}
|
|
163
163
|
/** Resize the canvas and update particles accordingly */
|
|
164
164
|
#resizeCanvas() {
|
|
165
|
-
const
|
|
166
|
-
const
|
|
165
|
+
const dpr = window.devicePixelRatio || 1;
|
|
166
|
+
const width = (this.canvas.width = this.canvas.rect.width * dpr);
|
|
167
|
+
const height = (this.canvas.height = this.canvas.rect.height * dpr);
|
|
168
|
+
this.ctx.scale(dpr, dpr);
|
|
167
169
|
// Hide the mouse when resizing because it must be outside the viewport to do so
|
|
168
170
|
this.mouseX = Infinity;
|
|
169
171
|
this.mouseY = Infinity;
|
|
@@ -181,9 +183,10 @@ class CanvasParticles {
|
|
|
181
183
|
if (this.isAnimating)
|
|
182
184
|
this.#render();
|
|
183
185
|
}
|
|
184
|
-
/** Update the canvas bounding rectangle, resize the canvas and update particles accordingly */
|
|
185
|
-
resizeCanvas() {
|
|
186
|
-
|
|
186
|
+
/** Update the canvas bounding rectangle (optional), resize the canvas and update particles accordingly */
|
|
187
|
+
resizeCanvas(updateRect = true) {
|
|
188
|
+
if (updateRect)
|
|
189
|
+
this.updateCanvasRect();
|
|
187
190
|
this.#resizeCanvas();
|
|
188
191
|
}
|
|
189
192
|
/** Update the target number of particles based on the current canvas size and `option.particles.ppm`, capped at `option.particles.max`. */
|
package/dist/index.d.ts
CHANGED
|
@@ -51,8 +51,8 @@ export default class CanvasParticles {
|
|
|
51
51
|
handleMouseMove(event: MouseEvent): void;
|
|
52
52
|
handleScroll(): void;
|
|
53
53
|
updateMousePos(): void;
|
|
54
|
-
/** Update the canvas bounding rectangle, resize the canvas and update particles accordingly */
|
|
55
|
-
resizeCanvas(): void;
|
|
54
|
+
/** Update the canvas bounding rectangle (optional), resize the canvas and update particles accordingly */
|
|
55
|
+
resizeCanvas(updateRect?: boolean): void;
|
|
56
56
|
/** Remove existing particles and generate new ones */
|
|
57
57
|
newParticles({ keepAuto, keepManual }?: {
|
|
58
58
|
keepAuto?: boolean | undefined;
|
package/dist/index.mjs
CHANGED
|
@@ -20,7 +20,7 @@ function Mulberry32(seed) {
|
|
|
20
20
|
const prng = Mulberry32(Math.random() * 4294967296).next;
|
|
21
21
|
class CanvasParticles {
|
|
22
22
|
/** Version of the library, injected via Rollup replace plugin. */
|
|
23
|
-
static version = "4.4.
|
|
23
|
+
static version = "4.4.9";
|
|
24
24
|
static MAX_DT = 1000 / 30; // milliseconds between updates @ 30 FPS
|
|
25
25
|
static BASE_DT = 1000 / 60; // milliseconds between updates @ 60 FPS
|
|
26
26
|
/** Defines mouse interaction types with the particles */
|
|
@@ -160,8 +160,10 @@ class CanvasParticles {
|
|
|
160
160
|
}
|
|
161
161
|
/** Resize the canvas and update particles accordingly */
|
|
162
162
|
#resizeCanvas() {
|
|
163
|
-
const
|
|
164
|
-
const
|
|
163
|
+
const dpr = window.devicePixelRatio || 1;
|
|
164
|
+
const width = (this.canvas.width = this.canvas.rect.width * dpr);
|
|
165
|
+
const height = (this.canvas.height = this.canvas.rect.height * dpr);
|
|
166
|
+
this.ctx.scale(dpr, dpr);
|
|
165
167
|
// Hide the mouse when resizing because it must be outside the viewport to do so
|
|
166
168
|
this.mouseX = Infinity;
|
|
167
169
|
this.mouseY = Infinity;
|
|
@@ -179,9 +181,10 @@ class CanvasParticles {
|
|
|
179
181
|
if (this.isAnimating)
|
|
180
182
|
this.#render();
|
|
181
183
|
}
|
|
182
|
-
/** Update the canvas bounding rectangle, resize the canvas and update particles accordingly */
|
|
183
|
-
resizeCanvas() {
|
|
184
|
-
|
|
184
|
+
/** Update the canvas bounding rectangle (optional), resize the canvas and update particles accordingly */
|
|
185
|
+
resizeCanvas(updateRect = true) {
|
|
186
|
+
if (updateRect)
|
|
187
|
+
this.updateCanvasRect();
|
|
185
188
|
this.#resizeCanvas();
|
|
186
189
|
}
|
|
187
190
|
/** Update the target number of particles based on the current canvas size and `option.particles.ppm`, capped at `option.particles.max`. */
|
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}}}(4294967296*Math.random()).next;class e{static version="4.4.7";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()}for(const i of t){i.target.instance.#t()}});static defaultIfNaN(t,i){return 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 i<o?console.warn(new RangeError(`option.${t} was clamped to ${o} as ${i} is too low`)):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=[];hasManualParticles=!1;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.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}#t(){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;const s=this.option.particles.generationType;s!==e.generationType.OFF&&(s===e.generationType.NEW||0===this.particles.length?this.newParticles():s===e.generationType.MATCH&&this.matchParticleCount({updateBounds:!0})),this.isAnimating&&this.#i()}resizeCanvas(){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 e=i()*this.width,s=i()*this.height;this.createParticle(e,s,i()*t,(.5+.5*i())*this.option.particles.relSpeed,(.5+2*Math.pow(i(),5))*this.option.particles.relSize,!1)}createParticle(t,i,e,s,n,o=!0){const a={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:o};this.#n(a),this.particles.push(a),this.hasManualParticles=!0}#n(t){t.bounds={top:-t.size,right:this.canvas.width+t.size,bottom:this.canvas.height+t.size,left:-t.size}}updateParticles(){const t=this.option.particles.relSpeed,e=this.option.particles.relSize;for(const s of this.particles)s.speed=(.5+.5*i())*t,s.size=(.5+2*Math.pow(i(),5))*e,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,n=s.length,o=this.option.particles.connectDist,a=o*this.option.gravity.repulsive*t,r=o*this.option.gravity.pulling*t,c=(o/2)**2,l=o**2/256;for(let t=0;t<n;t++){const i=s[t];for(let o=t+1;o<n;o++){const t=s[o],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*a,s=-n*e,o=-h*e;i.velX-=s,i.velY-=o,t.velX+=s,t.velY+=o}if(!e)continue;const f=u*r,g=-n*f,m=-h*f;i.velX+=g,i.velY+=m,t.velX-=g,t.velY-=m}}}#a(i){const s=this.width,n=this.height,o=this.offX,a=this.offY,r=this.mouseX,c=this.mouseY,l=this.option.mouse.interactionType===e.interactionType.NONE,h=this.option.mouse.interactionType===e.interactionType.MOVE,p=this.option.mouse.connectDist,d=this.option.mouse.distRatio,u=this.option.particles.rotationSpeed*i,f=this.option.gravity.friction,g=this.option.gravity.maxVelocity,m=1-Math.pow(3/4,i);for(const e of this.particles){e.dir+=2*(Math.random()-.5)*u*i,e.dir%=t;const v=Math.sin(e.dir)*e.speed,y=Math.cos(e.dir)*e.speed;g>0&&(e.velX>g&&(e.velX=g),e.velX<-g&&(e.velX=-g),e.velY>g&&(e.velY=g),e.velY<-g&&(e.velY=-g)),e.posX+=(v+e.velX)*i,e.posY+=(y+e.velY)*i,e.posX%=s,e.posX<0&&(e.posX+=s),e.posY%=n,e.posY<0&&(e.posY+=n),e.velX*=Math.pow(f,i),e.velY*=Math.pow(f,i);const x=e.posX+o-r,M=e.posY+a-c;if(!l){const t=p/Math.hypot(x,M);d<t?(e.offX+=(t*x-x-e.offX)*m,e.offY+=(t*M-M-e.offY)*m):(e.offX-=e.offX*m,e.offY-=e.offY*m)}e.x=e.posX+e.offX,e.y=e.posY+e.offY,h&&(e.posX=e.x,e.posY=e.y),e.x+=o,e.y+=a,e.gridPos.x=+(e.x>=e.bounds.left)+ +(e.x>e.bounds.right),e.gridPos.y=+(e.y>=e.bounds.top)+ +(e.y>e.bounds.bottom),e.isVisible=1===e.gridPos.x&&1===e.gridPos.y}}#r(){const i=this.ctx;for(const e of this.particles)e.isVisible&&(e.size>1?(i.beginPath(),i.arc(e.x,e.y,e.size,0,t),i.fill(),i.closePath()):i.fillRect(e.x-e.size,e.y-e.size,2*e.size,2*e.size))}#c(t,i){const e=this.particles,s=e.length,n=new Map;for(let o=0;o<s;o++){const s=e[o],a=(s.x*i|0)+Math.imul(s.y*i,t),r=n.get(a);r?r.push(o):n.set(a,[o])}return n}static#l(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)}#h(){const t=this.particles,i=t.length,s=this.ctx,n=this.option.particles.connectDist,o=n**2,a=(n/2)**2,r=1/n,c=Math.ceil(this.width*r),l=n>=Math.min(this.canvas.width,this.canvas.height),h=o*this.option.particles.maxWork,p=this.color.alpha,d=this.color.alpha*n,u=[],f=this.#c(c,r);let g=0,m=!0;function v(t,i,e,n){const r=t-e,c=i-n,l=r*r+c*c;l>o||(l>a?(s.globalAlpha=d/Math.sqrt(l)-p,s.beginPath(),s.moveTo(t,i),s.lineTo(e,n),s.stroke()):u.push(t,i,e,n),g+=l,m=g<h)}function y(i,s,n){for(const o of i){if(s>=o)continue;const i=t[o];if((l||e.#l(n,i))&&(v(n.x,n.y,i.x,i.y),!m))break}}function x(i,s){for(const n of i){const i=t[n];if((l||e.#l(s,i))&&(v(s.x,s.y,i.x,i.y),!m))break}}for(let e=0;e<i;e++){g=0,m=!0;let s,n=t[e],o=n.x*r|0,a=n.y*r|0,l=o+Math.imul(a,c);if((s=f.get(l+1))&&x(s,n),m&&((s=f.get(l+c))&&x(s,n),m&&((s=f.get(l+c+1))&&x(s,n),m&&((s=f.get(l+c-1))&&x(s,n),m)))){if(o>=0&&a>=0&&o<c-2&&(s=f.get(l))&&y(s||[],e,n),++e>=i)break;if(g=0,m=!0,n=t[e],o=n.x*r|0,a=n.y*r|0,l=o+Math.imul(a,c),(s=f.get(l+c+1))&&x(s,n),m&&((s=f.get(l+c-1))&&x(s,n),m&&((s=f.get(l+1))&&x(s,n),m&&((s=f.get(l+c))&&x(s,n),m)))){if(o>=0&&a>=0&&o<c-2&&(s=f.get(l))&&y(s||[],e,n),++e>=i)break;g=0,m=!0,n=t[e],o=n.x*r|0,a=n.y*r|0,l=o+Math.imul(a,c),(s=f.get(l+c))&&x(s,n),m&&((s=f.get(l+1))&&x(s,n),m&&(o>=0&&a>=0&&o<c-2&&(s=f.get(l))&&y(s||[],e,n),m&&((s=f.get(l+c-1))&&x(s,n),m&&(s=f.get(l+c+1))&&x(s,n))))}}}if(u.length){s.globalAlpha=p,s.beginPath();for(let t=0;t<u.length;t+=4)s.moveTo(u[t],u[t+1]),s.lineTo(u[t+2],u[t+3]);s.stroke()}}#p(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()}#d(){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()}#u(){const t=performance.now(),i=Math.min(t-this.lastAnimationFrame,e.MAX_DT)/e.BASE_DT;this.#o(i),this.#a(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.#r(),this.option.particles.drawLines&&this.#h(),this.option.debug.drawGrid&&this.#p(this.option.particles.connectDist),this.option.debug.drawIndexes&&this.#d()}#f(){this.isAnimating&&(requestAnimationFrame(()=>this.#f()),this.#u(),this.#i())}start({auto:t=!1}={}){return this.isAnimating||t&&!this.enableAnimating||(this.enableAnimating=!0,this.isAnimating=!0,this.updateCanvasRect(),requestAnimationFrame(()=>this.#f())),!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:{generationType:~~i("particles.generationType",t.particles?.generationType,e.generationType.MATCH,{min:0,max:2}),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}),maxVelocity:i("gravity.maxVelocity",t.gravity?.maxVelocity,1/0,{min:0})},debug:{drawGrid:!!t.debug?.drawGrid,drawIndexes:!!t.debug?.drawIndexes}},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});
|
|
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}}}(4294967296*Math.random()).next;class e{static version="4.4.9";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()}for(const i of t){i.target.instance.#t()}});static defaultIfNaN(t,i){return 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 i<o?console.warn(new RangeError(`option.${t} was clamped to ${o} as ${i} is too low`)):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=[];hasManualParticles=!1;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.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}#t(){const t=window.devicePixelRatio||1,i=this.canvas.width=this.canvas.rect.width*t,s=this.canvas.height=this.canvas.rect.height*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(s+2*this.option.particles.connectDist,1),this.offX=(i-this.width)/2,this.offY=(s-this.height)/2;const n=this.option.particles.generationType;n!==e.generationType.OFF&&(n===e.generationType.NEW||0===this.particles.length?this.newParticles():n===e.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 e=i()*this.width,s=i()*this.height;this.createParticle(e,s,i()*t,(.5+.5*i())*this.option.particles.relSpeed,(.5+2*Math.pow(i(),5))*this.option.particles.relSize,!1)}createParticle(t,i,e,s,n,o=!0){const a={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:o};this.#n(a),this.particles.push(a),this.hasManualParticles=!0}#n(t){t.bounds={top:-t.size,right:this.canvas.width+t.size,bottom:this.canvas.height+t.size,left:-t.size}}updateParticles(){const t=this.option.particles.relSpeed,e=this.option.particles.relSize;for(const s of this.particles)s.speed=(.5+.5*i())*t,s.size=(.5+2*Math.pow(i(),5))*e,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,n=s.length,o=this.option.particles.connectDist,a=o*this.option.gravity.repulsive*t,r=o*this.option.gravity.pulling*t,c=(o/2)**2,l=o**2/256;for(let t=0;t<n;t++){const i=s[t];for(let o=t+1;o<n;o++){const t=s[o],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*a,s=-n*e,o=-h*e;i.velX-=s,i.velY-=o,t.velX+=s,t.velY+=o}if(!e)continue;const f=u*r,g=-n*f,m=-h*f;i.velX+=g,i.velY+=m,t.velX-=g,t.velY-=m}}}#a(i){const s=this.width,n=this.height,o=this.offX,a=this.offY,r=this.mouseX,c=this.mouseY,l=this.option.mouse.interactionType===e.interactionType.NONE,h=this.option.mouse.interactionType===e.interactionType.MOVE,p=this.option.mouse.connectDist,d=this.option.mouse.distRatio,u=this.option.particles.rotationSpeed*i,f=this.option.gravity.friction,g=this.option.gravity.maxVelocity,m=1-Math.pow(3/4,i);for(const e of this.particles){e.dir+=2*(Math.random()-.5)*u*i,e.dir%=t;const v=Math.sin(e.dir)*e.speed,x=Math.cos(e.dir)*e.speed;g>0&&(e.velX>g&&(e.velX=g),e.velX<-g&&(e.velX=-g),e.velY>g&&(e.velY=g),e.velY<-g&&(e.velY=-g)),e.posX+=(v+e.velX)*i,e.posY+=(x+e.velY)*i,e.posX%=s,e.posX<0&&(e.posX+=s),e.posY%=n,e.posY<0&&(e.posY+=n),e.velX*=Math.pow(f,i),e.velY*=Math.pow(f,i);const y=e.posX+o-r,M=e.posY+a-c;if(!l){const t=p/Math.hypot(y,M);d<t?(e.offX+=(t*y-y-e.offX)*m,e.offY+=(t*M-M-e.offY)*m):(e.offX-=e.offX*m,e.offY-=e.offY*m)}e.x=e.posX+e.offX,e.y=e.posY+e.offY,h&&(e.posX=e.x,e.posY=e.y),e.x+=o,e.y+=a,e.gridPos.x=+(e.x>=e.bounds.left)+ +(e.x>e.bounds.right),e.gridPos.y=+(e.y>=e.bounds.top)+ +(e.y>e.bounds.bottom),e.isVisible=1===e.gridPos.x&&1===e.gridPos.y}}#r(){const i=this.ctx;for(const e of this.particles)e.isVisible&&(e.size>1?(i.beginPath(),i.arc(e.x,e.y,e.size,0,t),i.fill(),i.closePath()):i.fillRect(e.x-e.size,e.y-e.size,2*e.size,2*e.size))}#c(t,i){const e=this.particles,s=e.length,n=new Map;for(let o=0;o<s;o++){const s=e[o],a=(s.x*i|0)+Math.imul(s.y*i,t),r=n.get(a);r?r.push(o):n.set(a,[o])}return n}static#l(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)}#h(){const t=this.particles,i=t.length,s=this.ctx,n=this.option.particles.connectDist,o=n**2,a=(n/2)**2,r=1/n,c=Math.ceil(this.width*r),l=n>=Math.min(this.canvas.width,this.canvas.height),h=o*this.option.particles.maxWork,p=this.color.alpha,d=this.color.alpha*n,u=[],f=this.#c(c,r);let g=0,m=!0;function v(t,i,e,n){const r=t-e,c=i-n,l=r*r+c*c;l>o||(l>a?(s.globalAlpha=d/Math.sqrt(l)-p,s.beginPath(),s.moveTo(t,i),s.lineTo(e,n),s.stroke()):u.push(t,i,e,n),g+=l,m=g<h)}function x(i,s,n){for(const o of i){if(s>=o)continue;const i=t[o];if((l||e.#l(n,i))&&(v(n.x,n.y,i.x,i.y),!m))break}}function y(i,s){for(const n of i){const i=t[n];if((l||e.#l(s,i))&&(v(s.x,s.y,i.x,i.y),!m))break}}for(let e=0;e<i;e++){g=0,m=!0;let s,n=t[e],o=n.x*r|0,a=n.y*r|0,l=o+Math.imul(a,c);if((s=f.get(l+1))&&y(s,n),m&&((s=f.get(l+c))&&y(s,n),m&&((s=f.get(l+c+1))&&y(s,n),m&&((s=f.get(l+c-1))&&y(s,n),m)))){if(o>=0&&a>=0&&o<c-2&&(s=f.get(l))&&x(s||[],e,n),++e>=i)break;if(g=0,m=!0,n=t[e],o=n.x*r|0,a=n.y*r|0,l=o+Math.imul(a,c),(s=f.get(l+c+1))&&y(s,n),m&&((s=f.get(l+c-1))&&y(s,n),m&&((s=f.get(l+1))&&y(s,n),m&&((s=f.get(l+c))&&y(s,n),m)))){if(o>=0&&a>=0&&o<c-2&&(s=f.get(l))&&x(s||[],e,n),++e>=i)break;g=0,m=!0,n=t[e],o=n.x*r|0,a=n.y*r|0,l=o+Math.imul(a,c),(s=f.get(l+c))&&y(s,n),m&&((s=f.get(l+1))&&y(s,n),m&&(o>=0&&a>=0&&o<c-2&&(s=f.get(l))&&x(s||[],e,n),m&&((s=f.get(l+c-1))&&y(s,n),m&&(s=f.get(l+c+1))&&y(s,n))))}}}if(u.length){s.globalAlpha=p,s.beginPath();for(let t=0;t<u.length;t+=4)s.moveTo(u[t],u[t+1]),s.lineTo(u[t+2],u[t+3]);s.stroke()}}#p(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()}#d(){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()}#u(){const t=performance.now(),i=Math.min(t-this.lastAnimationFrame,e.MAX_DT)/e.BASE_DT;this.#o(i),this.#a(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.#r(),this.option.particles.drawLines&&this.#h(),this.option.debug.drawGrid&&this.#p(this.option.particles.connectDist),this.option.debug.drawIndexes&&this.#d()}#f(){this.isAnimating&&(requestAnimationFrame(()=>this.#f()),this.#u(),this.#i())}start({auto:t=!1}={}){return this.isAnimating||t&&!this.enableAnimating||(this.enableAnimating=!0,this.isAnimating=!0,this.updateCanvasRect(),requestAnimationFrame(()=>this.#f())),!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:{generationType:~~i("particles.generationType",t.particles?.generationType,e.generationType.MATCH,{min:0,max:2}),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}),maxVelocity:i("gravity.maxVelocity",t.gravity?.maxVelocity,1/0,{min:0})},debug:{drawGrid:!!t.debug?.drawGrid,drawIndexes:!!t.debug?.drawIndexes}},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
|
@@ -200,8 +200,10 @@ export default class CanvasParticles {
|
|
|
200
200
|
|
|
201
201
|
/** Resize the canvas and update particles accordingly */
|
|
202
202
|
#resizeCanvas() {
|
|
203
|
-
const
|
|
204
|
-
const
|
|
203
|
+
const dpr = window.devicePixelRatio || 1
|
|
204
|
+
const width = (this.canvas.width = this.canvas.rect.width * dpr)
|
|
205
|
+
const height = (this.canvas.height = this.canvas.rect.height * dpr)
|
|
206
|
+
this.ctx.scale(dpr, dpr)
|
|
205
207
|
|
|
206
208
|
// Hide the mouse when resizing because it must be outside the viewport to do so
|
|
207
209
|
this.mouseX = Infinity
|
|
@@ -222,9 +224,9 @@ export default class CanvasParticles {
|
|
|
222
224
|
if (this.isAnimating) this.#render()
|
|
223
225
|
}
|
|
224
226
|
|
|
225
|
-
/** Update the canvas bounding rectangle, resize the canvas and update particles accordingly */
|
|
226
|
-
resizeCanvas() {
|
|
227
|
-
this.updateCanvasRect()
|
|
227
|
+
/** Update the canvas bounding rectangle (optional), resize the canvas and update particles accordingly */
|
|
228
|
+
resizeCanvas(updateRect = true) {
|
|
229
|
+
if (updateRect) this.updateCanvasRect()
|
|
228
230
|
this.#resizeCanvas()
|
|
229
231
|
}
|
|
230
232
|
|