canvasparticles-js 4.5.1 → 4.5.4

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/README.md CHANGED
@@ -354,7 +354,7 @@ instance.options = { ... }
354
354
  createParticle(posX?: number, posY?: number, dir?: number, speed?: number, size?: number)
355
355
  ```
356
356
 
357
- By default `particles.ppm` and `particles.max` are used to auto-generate random particles. Set one or both of these properties to `0` or set `particles.generationType` to `OFF (0)` (slightly more performant).
357
+ By default `particles.ppm` and `particles.max` are used to auto-generate random particles. To turn this off, set at least one of these properties to `0` or set `particles.generationType` to `OFF (0)` (slightly more efficient).
358
358
 
359
359
  ```js
360
360
  const canvas = '#my-canvas'
@@ -391,7 +391,7 @@ instance.newParticles({ keepAuto: true, keepManual: false })
391
391
  left: 0;
392
392
  width: 100%;
393
393
  height: 100%;
394
- z-index: -1; /* Place behind other elements to act as background */
394
+ z-index: -1;
395
395
  }
396
396
  </style>
397
397
  </head>
package/dist/index.cjs CHANGED
@@ -33,13 +33,13 @@ function Mulberry32(seed) {
33
33
  },
34
34
  };
35
35
  }
36
- // Mulberry32 is ±392% faster than Math.random()
36
+ // Mulberry32 is x4 faster than Math.random()
37
37
  // Benchmark: https://jsbm.dev/muLCWR9RJCbmy
38
38
  // Spectral test: /demo/mulberry32.html
39
39
  const prng = Mulberry32(Math.random() * 4294967296).next;
40
40
  class CanvasParticles {
41
41
  /** Version of the library, injected via Rollup replace plugin. */
42
- static version = "4.5.1";
42
+ static version = "4.5.4";
43
43
  static MAX_DT = 1000 / 30; // milliseconds between updates @ 30 FPS
44
44
  static BASE_DT = 1000 / 60; // milliseconds between updates @ 60 FPS
45
45
  /** Defines mouse interaction types with the particles */
@@ -162,10 +162,12 @@ class CanvasParticles {
162
162
  }
163
163
  /** Resize the canvas and update particles accordingly */
164
164
  #resizeCanvas(dpr = window.devicePixelRatio || 1) {
165
+ if (dpr < 1)
166
+ dpr = 1;
165
167
  const width = (this.canvas.width = this.canvas.rect.width * dpr);
166
168
  const height = (this.canvas.height = this.canvas.rect.height * dpr);
167
169
  // Must be set every time width or height changes because scale is removed
168
- if (dpr !== 1)
170
+ if (dpr > 1)
169
171
  this.ctx.scale(dpr, dpr);
170
172
  // Hide the mouse when resizing because it must be outside the viewport to do so
171
173
  this.mouseX = Infinity;
@@ -442,11 +444,12 @@ class CanvasParticles {
442
444
  /** Draw the particles on the canvas */
443
445
  #renderParticles() {
444
446
  const ctx = this.ctx;
447
+ const dpr = window.devicePixelRatio || 1;
445
448
  for (const p of this.particles) {
446
449
  if (!p.isVisible)
447
450
  continue;
448
451
  // Draw particles smaller than 1px as a square instead of a circle for performance
449
- if (p.size > 1) {
452
+ if (p.size > 1 / dpr) {
450
453
  // Draw circle
451
454
  ctx.beginPath();
452
455
  ctx.arc(p.x, p.y, p.size, 0, TWO_PI);
@@ -700,7 +703,7 @@ class CanvasParticles {
700
703
  }
701
704
  /** Clear the canvas and render the particles and their connections onto the canvas */
702
705
  #render() {
703
- this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
706
+ this.ctx.clearRect(0, 0, this.canvas.rect.width, this.canvas.rect.height);
704
707
  this.ctx.globalAlpha = this.color.alpha;
705
708
  this.ctx.fillStyle = this.color.hex;
706
709
  this.ctx.strokeStyle = this.color.hex;
@@ -740,7 +743,7 @@ class CanvasParticles {
740
743
  this.enableAnimating = false;
741
744
  this.isAnimating = false;
742
745
  if (clear !== false)
743
- this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
746
+ this.ctx.clearRect(0, 0, this.canvas.rect.width, this.canvas.rect.height);
744
747
  return true;
745
748
  }
746
749
  /** Gracefully destroy the instance and remove the canvas element */
package/dist/index.mjs CHANGED
@@ -31,13 +31,13 @@ function Mulberry32(seed) {
31
31
  },
32
32
  };
33
33
  }
34
- // Mulberry32 is ±392% faster than Math.random()
34
+ // Mulberry32 is x4 faster than Math.random()
35
35
  // Benchmark: https://jsbm.dev/muLCWR9RJCbmy
36
36
  // Spectral test: /demo/mulberry32.html
37
37
  const prng = Mulberry32(Math.random() * 4294967296).next;
38
38
  class CanvasParticles {
39
39
  /** Version of the library, injected via Rollup replace plugin. */
40
- static version = "4.5.1";
40
+ static version = "4.5.4";
41
41
  static MAX_DT = 1000 / 30; // milliseconds between updates @ 30 FPS
42
42
  static BASE_DT = 1000 / 60; // milliseconds between updates @ 60 FPS
43
43
  /** Defines mouse interaction types with the particles */
@@ -160,10 +160,12 @@ class CanvasParticles {
160
160
  }
161
161
  /** Resize the canvas and update particles accordingly */
162
162
  #resizeCanvas(dpr = window.devicePixelRatio || 1) {
163
+ if (dpr < 1)
164
+ dpr = 1;
163
165
  const width = (this.canvas.width = this.canvas.rect.width * dpr);
164
166
  const height = (this.canvas.height = this.canvas.rect.height * dpr);
165
167
  // Must be set every time width or height changes because scale is removed
166
- if (dpr !== 1)
168
+ if (dpr > 1)
167
169
  this.ctx.scale(dpr, dpr);
168
170
  // Hide the mouse when resizing because it must be outside the viewport to do so
169
171
  this.mouseX = Infinity;
@@ -440,11 +442,12 @@ class CanvasParticles {
440
442
  /** Draw the particles on the canvas */
441
443
  #renderParticles() {
442
444
  const ctx = this.ctx;
445
+ const dpr = window.devicePixelRatio || 1;
443
446
  for (const p of this.particles) {
444
447
  if (!p.isVisible)
445
448
  continue;
446
449
  // Draw particles smaller than 1px as a square instead of a circle for performance
447
- if (p.size > 1) {
450
+ if (p.size > 1 / dpr) {
448
451
  // Draw circle
449
452
  ctx.beginPath();
450
453
  ctx.arc(p.x, p.y, p.size, 0, TWO_PI);
@@ -698,7 +701,7 @@ class CanvasParticles {
698
701
  }
699
702
  /** Clear the canvas and render the particles and their connections onto the canvas */
700
703
  #render() {
701
- this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
704
+ this.ctx.clearRect(0, 0, this.canvas.rect.width, this.canvas.rect.height);
702
705
  this.ctx.globalAlpha = this.color.alpha;
703
706
  this.ctx.fillStyle = this.color.hex;
704
707
  this.ctx.strokeStyle = this.color.hex;
@@ -738,7 +741,7 @@ class CanvasParticles {
738
741
  this.enableAnimating = false;
739
742
  this.isAnimating = false;
740
743
  if (clear !== false)
741
- this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
744
+ this.ctx.clearRect(0, 0, this.canvas.rect.width, this.canvas.rect.height);
742
745
  return true;
743
746
  }
744
747
  /** Gracefully destroy the instance and remove the canvas element */
package/dist/index.umd.js CHANGED
@@ -1 +1 @@
1
- !function(t,i){"object"==typeof exports&&"undefined"!=typeof module?module.exports=i():"function"==typeof define&&define.amd?define(i):(t="undefined"!=typeof globalThis?globalThis:t||self).CanvasParticles=i()}(this,function(){"use strict";function t(t,i,e,s){if(null==i)return e;const{min:n=-1/0,max:o=1/0}=s??{};return i<n?console.warn(`option.${t} was clamped to ${n} as ${i} is too low`):i>o&&console.warn(`option.${t} was clamped to ${o} as ${i} is too high`),function(t,i){return isNaN(+t)?i:+t}(Math.min(Math.max(i??e,n),o),e)}const i=2*Math.PI;const e=function(t){let i=t>>>0;return{next(){let t=i+1831565813|0;return i=t,t=Math.imul(t^t>>>15,1|t),t^=t+Math.imul(t^t>>>7,61|t),((t^t>>>14)>>>0)/4294967296}}}(4294967296*Math.random()).next;class s{static version="4.5.1";static MAX_DT=1e3/30;static BASE_DT=1e3/60;static interactionType=Object.freeze({NONE:0,SHIFT:1,MOVE:2});static generationType=Object.freeze({OFF:0,NEW:1,MATCH:2});static canvasIntersectionObserver=new IntersectionObserver(t=>{for(const i of t){const t=i.target,e=t.instance;if(!e.options?.animation)return;(t.inViewbox=i.isIntersecting)?e.option.animation?.startOnEnter&&e.start({auto:!0}):e.option.animation?.stopOnLeave&&e.stop({auto:!0,clear:!1})}},{rootMargin:"-1px"});static canvasResizeObserver=new ResizeObserver(t=>{for(const i of t){i.target.instance.updateCanvasRect()}const i=window.devicePixelRatio||1;for(const e of t){e.target.instance.#t(i)}});static instances=new Set;canvas;ctx;enableAnimating=!1;isAnimating=!1;lastAnimationFrame=0;particles=[];hasManualParticles=!1;clientX=1/0;clientY=1/0;mouseX=1/0;mouseY=1/0;dpr=1;width;height;offX;offY;option;color;constructor(t,i={}){let e;if(t instanceof HTMLCanvasElement)e=t;else{if("string"!=typeof t)throw new TypeError("selector is not a string and neither a HTMLCanvasElement itself");if(e=document.querySelector(t),!(e instanceof HTMLCanvasElement))throw new Error("selector does not point to a canvas")}this.canvas=e,this.canvas.instance=this,this.canvas.inViewbox=!0;const n=this.canvas.getContext("2d");if(!n)throw new Error("failed to get 2D context from canvas");this.ctx=n,this.options=i,s.instances.add(this),s.canvasIntersectionObserver.observe(this.canvas),s.canvasResizeObserver.observe(this.canvas)}updateCanvasRect(){const{top:t,left:i,width:e,height:s}=this.canvas.getBoundingClientRect();this.canvas.rect={top:t,left:i,width:e,height:s}}handleMouseMove(t){this.enableAnimating&&(this.clientX=t.clientX,this.clientY=t.clientY,this.isAnimating&&this.updateMousePos())}handleScroll(){this.enableAnimating&&(this.updateCanvasRect(),this.isAnimating&&this.updateMousePos())}updateMousePos(){const{top:t,left:i}=this.canvas.rect;this.mouseX=this.clientX-i,this.mouseY=this.clientY-t}#t(t=window.devicePixelRatio||1){const i=this.canvas.width=this.canvas.rect.width*t,e=this.canvas.height=this.canvas.rect.height*t;1!==t&&this.ctx.scale(t,t),this.mouseX=1/0,this.mouseY=1/0,this.width=Math.max(i+2*this.option.particles.connectDist,1),this.height=Math.max(e+2*this.option.particles.connectDist,1),this.offX=(i-this.width)/2,this.offY=(e-this.height)/2;const n=this.option.particles.generationType;n!==s.generationType.OFF&&(n===s.generationType.NEW||0===this.particles.length?this.newParticles():n===s.generationType.MATCH&&this.matchParticleCount({updateBounds:!0})),this.isAnimating&&this.#i()}resizeCanvas(t=!0){t&&this.updateCanvasRect(),this.#t()}#e(){let t=Math.round(this.option.particles.ppm*this.width*this.height/1e6);if(t=Math.min(this.option.particles.max,t),!isFinite(t))throw new RangeError("particleCount must be finite");return 0|t}newParticles({keepAuto:t=!1,keepManual:i=!0}={}){const e=this.#e();if(this.hasManualParticles&&(t||i)?(this.particles=this.particles.filter(e=>t&&!e.isManual||i&&e.isManual),this.hasManualParticles=this.particles.length>0):this.particles=[],!t)for(let t=0;t<e;t++)this.#s()}matchParticleCount({updateBounds:t=!1}={}){const i=this.#e();if(this.hasManualParticles){const t=[];let e=0;for(const s of this.particles)s.isManual?t.push(s):e<i&&(t.push(s),e++);this.particles=t}else this.particles=this.particles.slice(0,i);if(t)for(const t of this.particles)this.#n(t);for(let t=this.particles.length;t<i;t++)this.#s()}#s(){const t=e()*this.width,s=e()*this.height;this.createParticle(t,s,e()*i,(.5+.5*e())*this.option.particles.relSpeed,(.5+2*Math.pow(e(),5))*this.option.particles.relSize,!1)}createParticle(t,i,e,s,n,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,bounds:{top:-n,right:this.canvas.width+n,bottom:this.canvas.height+n,left:-n}};this.particles.push(a),this.hasManualParticles=!0}#o(t){t.bounds.top=-t.size,t.bounds.right=this.canvas.width+t.size,t.bounds.bottom=this.canvas.height+t.size,t.bounds.left=-t.size}#n(t){t.bounds.right=this.canvas.width+t.size,t.bounds.bottom=this.canvas.height+t.size}updateParticles(){const t=this.option.particles.relSpeed,i=this.option.particles.relSize;for(const s of this.particles)s.speed=(.5+.5*e())*t,s.size=(.5+2*Math.pow(e(),5))*i,this.#o(s)}#a(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}}}#r(t){const e=this.width,n=this.height,o=this.offX,a=this.offY,r=this.mouseX,c=this.mouseY,l=this.option.mouse.interactionType===s.interactionType.NONE,h=this.option.mouse.interactionType===s.interactionType.MOVE,p=this.option.mouse.connectDist,d=this.option.mouse.distRatio,u=this.option.particles.rotationSpeed*t,f=this.option.gravity.friction,g=this.option.gravity.maxVelocity,m=1-Math.pow(3/4,t);for(const s of this.particles){s.dir+=2*(Math.random()-.5)*u*t,s.dir%=i;const v=Math.sin(s.dir)*s.speed,x=Math.cos(s.dir)*s.speed;g>0&&(s.velX>g&&(s.velX=g),s.velX<-g&&(s.velX=-g),s.velY>g&&(s.velY=g),s.velY<-g&&(s.velY=-g)),s.posX+=(v+s.velX)*t,s.posY+=(x+s.velY)*t,s.posX%=e,s.posX<0&&(s.posX+=e),s.posY%=n,s.posY<0&&(s.posY+=n),s.velX*=Math.pow(f,t),s.velY*=Math.pow(f,t);const y=s.posX+o-r,b=s.posY+a-c;if(!l){const t=p/Math.hypot(y,b);d<t?(s.offX+=(t*y-y-s.offX)*m,s.offY+=(t*b-b-s.offY)*m):(s.offX-=s.offX*m,s.offY-=s.offY*m)}s.x=s.posX+s.offX,s.y=s.posY+s.offY,h&&(s.posX=s.x,s.posY=s.y),s.x+=o,s.y+=a,s.gridPos.x=+(s.x>=s.bounds.left)+ +(s.x>s.bounds.right),s.gridPos.y=+(s.y>=s.bounds.top)+ +(s.y>s.bounds.bottom),s.isVisible=1===s.gridPos.x&&1===s.gridPos.y}}#c(){const t=this.ctx;for(const e of this.particles)e.isVisible&&(e.size>1?(t.beginPath(),t.arc(e.x,e.y,e.size,0,i),t.fill(),t.closePath()):t.fillRect(e.x-e.size,e.y-e.size,2*e.size,2*e.size))}#l(t,i){const e=this.particles,s=e.length,n=new Map;for(let 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#h(t,i){return!(!t.isVisible&&!i.isVisible)||!(t.gridPos.x===i.gridPos.x&&1!==t.gridPos.x||t.gridPos.y===i.gridPos.y&&1!==t.gridPos.y)}#p(){const t=this.particles,i=t.length,e=this.ctx,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.#l(c,r);let g=0,m=!0;function v(t,i,s,n){const r=t-s,c=i-n,l=r*r+c*c;l>o||(l>a?(e.globalAlpha=d/Math.sqrt(l)-p,e.beginPath(),e.moveTo(t,i),e.lineTo(s,n),e.stroke()):u.push(t,i,s,n),g+=l,m=g<h)}function x(i,e,n){for(const o of i){if(e>=o)continue;const i=t[o];if((l||s.#h(n,i))&&(v(n.x,n.y,i.x,i.y),!m))break}}function y(i,e){for(const n of i){const i=t[n];if((l||s.#h(e,i))&&(v(e.x,e.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){e.globalAlpha=p,e.beginPath();for(let t=0;t<u.length;t+=4)e.moveTo(u[t],u[t+1]),e.lineTo(u[t+2],u[t+3]);e.stroke()}}#d(t){const i=this.ctx,{width:e,height:s}=this.canvas;i.save(),i.globalAlpha=.5,i.beginPath();for(let n=.5;n<=e;n+=t)i.moveTo(n,0),i.lineTo(n,s);for(let n=.5;n<=s;n+=t)i.moveTo(0,n),i.lineTo(e,n);i.stroke(),i.restore()}#u(){const t=this.ctx,i=this.particles,e=i.length;t.save(),t.globalAlpha=1,t.fillStyle="#fff",t.textAlign="center",t.textBaseline="middle";for(let s=0;s<e;s++){const e=i[s];t.fillText(String(s),e.x,e.y)}t.restore()}#f(){const t=performance.now(),i=Math.min(t-this.lastAnimationFrame,s.MAX_DT)/s.BASE_DT;this.#a(i),this.#r(i),this.lastAnimationFrame=t}#i(){this.ctx.clearRect(0,0,this.canvas.width,this.canvas.height),this.ctx.globalAlpha=this.color.alpha,this.ctx.fillStyle=this.color.hex,this.ctx.strokeStyle=this.color.hex,this.ctx.lineWidth=1,this.#c(),this.option.particles.drawLines&&this.#p(),this.option.debug.drawGrid&&this.#d(this.option.particles.connectDist),this.option.debug.drawIndexes&&this.#u()}#g(){this.isAnimating&&(requestAnimationFrame(()=>this.#g()),this.#f(),this.#i())}start({auto:t=!1}={}){return this.isAnimating||t&&!this.enableAnimating||(this.enableAnimating=!0,this.isAnimating=!0,this.updateCanvasRect(),requestAnimationFrame(()=>this.#g())),!this.canvas.inViewbox&&this.option.animation.startOnEnter&&(this.isAnimating=!1),this}stop({auto:t=!1,clear:i=!0}={}){return t||(this.enableAnimating=!1),this.isAnimating=!1,!1!==i&&this.ctx.clearRect(0,0,this.canvas.width,this.canvas.height),!0}destroy(){this.stop(),s.instances.delete(this),s.canvasIntersectionObserver.unobserve(this.canvas),s.canvasResizeObserver.unobserve(this.canvas),this.canvas?.remove(),Object.keys(this).forEach(t=>delete this[t])}set options(i){const e=t;this.option={background:i.background??!1,animation:{startOnEnter:!!(i.animation?.startOnEnter??1),stopOnLeave:!!(i.animation?.stopOnLeave??1)},mouse:{interactionType:~~e("mouse.interactionType",i.mouse?.interactionType,s.interactionType.MOVE,{min:0,max:2}),connectDist:1,distRatio:e("mouse.distRatio",i.mouse?.distRatio,2/3,{min:0})},particles:{generationType:~~e("particles.generationType",i.particles?.generationType,s.generationType.MATCH,{min:0,max:2}),drawLines:!!(i.particles?.drawLines??1),color:i.particles?.color??"black",ppm:~~e("particles.ppm",i.particles?.ppm,100),max:Math.round(e("particles.max",i.particles?.max,1/0,{min:0})),maxWork:Math.round(e("particles.maxWork",i.particles?.maxWork,1/0,{min:0})),connectDist:~~e("particles.connectDistance",i.particles?.connectDistance,150,{min:1}),relSpeed:e("particles.relSpeed",i.particles?.relSpeed,1,{min:0}),relSize:e("particles.relSize",i.particles?.relSize,1,{min:0}),rotationSpeed:e("particles.rotationSpeed",i.particles?.rotationSpeed,2,{min:0})/100},gravity:{repulsive:e("gravity.repulsive",i.gravity?.repulsive,0,{min:0}),pulling:e("gravity.pulling",i.gravity?.pulling,0,{min:0}),friction:e("gravity.friction",i.gravity?.friction,.8,{min:0,max:1}),maxVelocity:e("gravity.maxVelocity",i.gravity?.maxVelocity,1/0,{min:0})},debug:{drawGrid:!!i.debug?.drawGrid,drawIndexes:!!i.debug?.drawIndexes}},this.setBackground(this.option.background),this.setMouseConnectDistMult(i.mouse?.connectDistMult),this.setParticleColor(this.option.particles.color)}get options(){return this.option}setBackground(t){if(t){if("string"!=typeof t)throw new TypeError("background is not a string");this.canvas.style.background=this.option.background=t}}setMouseConnectDistMult(i){const e=t("mouse.connectDistMult",i,2/3,{min:0});this.option.mouse.connectDist=this.option.particles.connectDist*e}setParticleColor(t){if(this.ctx.fillStyle=t,"#"===String(this.ctx.fillStyle)[0])this.color={hex:String(this.ctx.fillStyle),alpha:1};else{let t=String(this.ctx.fillStyle).split(",").at(-1);t=t?.slice(1,-1)??"1",this.ctx.fillStyle=String(this.ctx.fillStyle).split(",").slice(0,-1).join(",")+", 1)",this.color={hex:String(this.ctx.fillStyle),alpha:isNaN(+t)?1:+t}}}}return window.addEventListener("mousemove",t=>{for(const i of s.instances)i.handleMouseMove(t)},{passive:!0}),window.addEventListener("scroll",()=>{for(const t of s.instances)t.handleScroll()},{passive:!0}),s});
1
+ !function(t,i){"object"==typeof exports&&"undefined"!=typeof module?module.exports=i():"function"==typeof define&&define.amd?define(i):(t="undefined"!=typeof globalThis?globalThis:t||self).CanvasParticles=i()}(this,function(){"use strict";function t(t,i,e,s){if(null==i)return e;const{min:n=-1/0,max:a=1/0}=s??{};return i<n?console.warn(`option.${t} was clamped to ${n} as ${i} is too low`):i>a&&console.warn(`option.${t} was clamped to ${a} as ${i} is too high`),function(t,i){return isNaN(+t)?i:+t}(Math.min(Math.max(i??e,n),a),e)}const i=2*Math.PI;const e=function(t){let i=t>>>0;return{next(){let t=i+1831565813|0;return i=t,t=Math.imul(t^t>>>15,1|t),t^=t+Math.imul(t^t>>>7,61|t),((t^t>>>14)>>>0)/4294967296}}}(4294967296*Math.random()).next;class CanvasParticles{static version="4.5.4";static MAX_DT=1e3/30;static BASE_DT=1e3/60;static interactionType=Object.freeze({NONE:0,SHIFT:1,MOVE:2});static generationType=Object.freeze({OFF:0,NEW:1,MATCH:2});static canvasIntersectionObserver=new IntersectionObserver(t=>{for(const i of t){const t=i.target,e=t.instance;if(!e.options?.animation)return;(t.inViewbox=i.isIntersecting)?e.option.animation?.startOnEnter&&e.start({auto:!0}):e.option.animation?.stopOnLeave&&e.stop({auto:!0,clear:!1})}},{rootMargin:"-1px"});static canvasResizeObserver=new ResizeObserver(t=>{for(const i of t){i.target.instance.updateCanvasRect()}const i=window.devicePixelRatio||1;for(const e of t){e.target.instance.#t(i)}});static instances=new Set;canvas;ctx;enableAnimating=!1;isAnimating=!1;lastAnimationFrame=0;particles=[];hasManualParticles=!1;clientX=1/0;clientY=1/0;mouseX=1/0;mouseY=1/0;dpr=1;width;height;offX;offY;option;color;constructor(t,i={}){let e;if(t instanceof HTMLCanvasElement)e=t;else{if("string"!=typeof t)throw new TypeError("selector is not a string and neither a HTMLCanvasElement itself");if(e=document.querySelector(t),!(e instanceof HTMLCanvasElement))throw new Error("selector does not point to a canvas")}this.canvas=e,this.canvas.instance=this,this.canvas.inViewbox=!0;const s=this.canvas.getContext("2d");if(!s)throw new Error("failed to get 2D context from canvas");this.ctx=s,this.options=i,CanvasParticles.instances.add(this),CanvasParticles.canvasIntersectionObserver.observe(this.canvas),CanvasParticles.canvasResizeObserver.observe(this.canvas)}updateCanvasRect(){const{top:t,left:i,width:e,height:s}=this.canvas.getBoundingClientRect();this.canvas.rect={top:t,left:i,width:e,height:s}}handleMouseMove(t){this.enableAnimating&&(this.clientX=t.clientX,this.clientY=t.clientY,this.isAnimating&&this.updateMousePos())}handleScroll(){this.enableAnimating&&(this.updateCanvasRect(),this.isAnimating&&this.updateMousePos())}updateMousePos(){const{top:t,left:i}=this.canvas.rect;this.mouseX=this.clientX-i,this.mouseY=this.clientY-t}#t(t=window.devicePixelRatio||1){t<1&&(t=1);const i=this.canvas.width=this.canvas.rect.width*t,e=this.canvas.height=this.canvas.rect.height*t;t>1&&this.ctx.scale(t,t),this.mouseX=1/0,this.mouseY=1/0,this.width=Math.max(i+2*this.option.particles.connectDist,1),this.height=Math.max(e+2*this.option.particles.connectDist,1),this.offX=(i-this.width)/2,this.offY=(e-this.height)/2;const s=this.option.particles.generationType;s!==CanvasParticles.generationType.OFF&&(s===CanvasParticles.generationType.NEW||0===this.particles.length?this.newParticles():s===CanvasParticles.generationType.MATCH&&this.matchParticleCount({updateBounds:!0})),this.isAnimating&&this.#i()}resizeCanvas(t=!0){t&&this.updateCanvasRect(),this.#t()}#e(){let t=Math.round(this.option.particles.ppm*this.width*this.height/1e6);if(t=Math.min(this.option.particles.max,t),!isFinite(t))throw new RangeError("particleCount must be finite");return 0|t}newParticles({keepAuto:t=!1,keepManual:i=!0}={}){const e=this.#e();if(this.hasManualParticles&&(t||i)?(this.particles=this.particles.filter(e=>t&&!e.isManual||i&&e.isManual),this.hasManualParticles=this.particles.length>0):this.particles=[],!t)for(let t=0;t<e;t++)this.#s()}matchParticleCount({updateBounds:t=!1}={}){const i=this.#e();if(this.hasManualParticles){const t=[];let e=0;for(const s of this.particles)s.isManual?t.push(s):e<i&&(t.push(s),e++);this.particles=t}else this.particles=this.particles.slice(0,i);if(t)for(const t of this.particles)this.#n(t);for(let t=this.particles.length;t<i;t++)this.#s()}#s(){const t=e()*this.width,s=e()*this.height;this.createParticle(t,s,e()*i,(.5+.5*e())*this.option.particles.relSpeed,(.5+2*Math.pow(e(),5))*this.option.particles.relSize,!1)}createParticle(t,i,e,s,n,a=!0){const o={posX:t,posY:i,x:t,y:i,velX:0,velY:0,offX:0,offY:0,dir:e,speed:s,size:n,gridPos:{x:1,y:1},isVisible:!1,isManual:a,bounds:{top:-n,right:this.canvas.width+n,bottom:this.canvas.height+n,left:-n}};this.particles.push(o),this.hasManualParticles=!0}#a(t){t.bounds.top=-t.size,t.bounds.right=this.canvas.width+t.size,t.bounds.bottom=this.canvas.height+t.size,t.bounds.left=-t.size}#n(t){t.bounds.right=this.canvas.width+t.size,t.bounds.bottom=this.canvas.height+t.size}updateParticles(){const t=this.option.particles.relSpeed,i=this.option.particles.relSize;for(const s of this.particles)s.speed=(.5+.5*e())*t,s.size=(.5+2*Math.pow(e(),5))*i,this.#a(s)}#o(t){const i=this.option.gravity.repulsive>0,e=this.option.gravity.pulling>0;if(!i&&!e)return;const s=this.particles,n=s.length,a=this.option.particles.connectDist,o=a*this.option.gravity.repulsive*t,r=a*this.option.gravity.pulling*t,c=(a/2)**2,l=a**2/256;for(let t=0;t<n;t++){const i=s[t];for(let a=t+1;a<n;a++){const t=s[a],n=i.posX-t.posX,h=i.posY-t.posY,p=n*n+h*h;if(p>=c&&!e)continue;const d=1/Math.sqrt(p+l),u=d*d*d;if(p<c){const e=u*o,s=-n*e,a=-h*e;i.velX-=s,i.velY-=a,t.velX+=s,t.velY+=a}if(!e)continue;const f=u*r,g=-n*f,v=-h*f;i.velX+=g,i.velY+=v,t.velX-=g,t.velY-=v}}}#r(t){const e=this.width,s=this.height,n=this.offX,a=this.offY,o=this.mouseX,r=this.mouseY,c=this.option.mouse.interactionType===CanvasParticles.interactionType.NONE,l=this.option.mouse.interactionType===CanvasParticles.interactionType.MOVE,h=this.option.mouse.connectDist,p=this.option.mouse.distRatio,d=this.option.particles.rotationSpeed*t,u=this.option.gravity.friction,f=this.option.gravity.maxVelocity,g=1-Math.pow(3/4,t);for(const v of this.particles){v.dir+=2*(Math.random()-.5)*d*t,v.dir%=i;const m=Math.sin(v.dir)*v.speed,x=Math.cos(v.dir)*v.speed;f>0&&(v.velX>f&&(v.velX=f),v.velX<-f&&(v.velX=-f),v.velY>f&&(v.velY=f),v.velY<-f&&(v.velY=-f)),v.posX+=(m+v.velX)*t,v.posY+=(x+v.velY)*t,v.posX%=e,v.posX<0&&(v.posX+=e),v.posY%=s,v.posY<0&&(v.posY+=s),v.velX*=Math.pow(u,t),v.velY*=Math.pow(u,t);const y=v.posX+n-o,b=v.posY+a-r;if(!c){const t=h/Math.hypot(y,b);p<t?(v.offX+=(t*y-y-v.offX)*g,v.offY+=(t*b-b-v.offY)*g):(v.offX-=v.offX*g,v.offY-=v.offY*g)}v.x=v.posX+v.offX,v.y=v.posY+v.offY,l&&(v.posX=v.x,v.posY=v.y),v.x+=n,v.y+=a,v.gridPos.x=+(v.x>=v.bounds.left)+ +(v.x>v.bounds.right),v.gridPos.y=+(v.y>=v.bounds.top)+ +(v.y>v.bounds.bottom),v.isVisible=1===v.gridPos.x&&1===v.gridPos.y}}#c(){const t=this.ctx,e=window.devicePixelRatio||1;for(const s of this.particles)s.isVisible&&(s.size>1/e?(t.beginPath(),t.arc(s.x,s.y,s.size,0,i),t.fill(),t.closePath()):t.fillRect(s.x-s.size,s.y-s.size,2*s.size,2*s.size))}#l(t,i){const e=this.particles,s=e.length,n=new Map;for(let a=0;a<s;a++){const s=e[a],o=(s.x*i|0)+Math.imul(s.y*i,t),r=n.get(o);r?r.push(a):n.set(o,[a])}return n}static#h(t,i){return!(!t.isVisible&&!i.isVisible)||!(t.gridPos.x===i.gridPos.x&&1!==t.gridPos.x||t.gridPos.y===i.gridPos.y&&1!==t.gridPos.y)}#p(){const t=this.particles,i=t.length,e=this.ctx,s=this.option.particles.connectDist,n=s**2,a=(s/2)**2,o=1/s,r=Math.ceil(this.width*o),c=s>=Math.min(this.canvas.width,this.canvas.height),l=n*this.option.particles.maxWork,h=this.color.alpha,p=this.color.alpha*s,d=[],u=this.#l(r,o);let f=0,g=!0;function v(t,i,s,o){const r=t-s,c=i-o,u=r*r+c*c;u>n||(u>a?(e.globalAlpha=p/Math.sqrt(u)-h,e.beginPath(),e.moveTo(t,i),e.lineTo(s,o),e.stroke()):d.push(t,i,s,o),f+=u,g=f<l)}function m(i,e,s){for(const n of i){if(e>=n)continue;const i=t[n];if((c||CanvasParticles.#h(s,i))&&(v(s.x,s.y,i.x,i.y),!g))break}}function x(i,e){for(const s of i){const i=t[s];if((c||CanvasParticles.#h(e,i))&&(v(e.x,e.y,i.x,i.y),!g))break}}for(let e=0;e<i;e++){f=0,g=!0;let s,n=t[e],a=n.x*o|0,c=n.y*o|0,l=a+Math.imul(c,r);if((s=u.get(l+1))&&x(s,n),g&&((s=u.get(l+r))&&x(s,n),g&&((s=u.get(l+r+1))&&x(s,n),g&&((s=u.get(l+r-1))&&x(s,n),g)))){if(a>=0&&c>=0&&a<r-2&&(s=u.get(l))&&m(s||[],e,n),++e>=i)break;if(f=0,g=!0,n=t[e],a=n.x*o|0,c=n.y*o|0,l=a+Math.imul(c,r),(s=u.get(l+r+1))&&x(s,n),g&&((s=u.get(l+r-1))&&x(s,n),g&&((s=u.get(l+1))&&x(s,n),g&&((s=u.get(l+r))&&x(s,n),g)))){if(a>=0&&c>=0&&a<r-2&&(s=u.get(l))&&m(s||[],e,n),++e>=i)break;f=0,g=!0,n=t[e],a=n.x*o|0,c=n.y*o|0,l=a+Math.imul(c,r),(s=u.get(l+r))&&x(s,n),g&&((s=u.get(l+1))&&x(s,n),g&&(a>=0&&c>=0&&a<r-2&&(s=u.get(l))&&m(s||[],e,n),g&&((s=u.get(l+r-1))&&x(s,n),g&&(s=u.get(l+r+1))&&x(s,n))))}}}if(d.length){e.globalAlpha=h,e.beginPath();for(let t=0;t<d.length;t+=4)e.moveTo(d[t],d[t+1]),e.lineTo(d[t+2],d[t+3]);e.stroke()}}#d(t){const i=this.ctx,{width:e,height:s}=this.canvas;i.save(),i.globalAlpha=.5,i.beginPath();for(let n=.5;n<=e;n+=t)i.moveTo(n,0),i.lineTo(n,s);for(let n=.5;n<=s;n+=t)i.moveTo(0,n),i.lineTo(e,n);i.stroke(),i.restore()}#u(){const t=this.ctx,i=this.particles,e=i.length;t.save(),t.globalAlpha=1,t.fillStyle="#fff",t.textAlign="center",t.textBaseline="middle";for(let s=0;s<e;s++){const e=i[s];t.fillText(String(s),e.x,e.y)}t.restore()}#f(){const t=performance.now(),i=Math.min(t-this.lastAnimationFrame,CanvasParticles.MAX_DT)/CanvasParticles.BASE_DT;this.#o(i),this.#r(i),this.lastAnimationFrame=t}#i(){this.ctx.clearRect(0,0,this.canvas.rect.width,this.canvas.rect.height),this.ctx.globalAlpha=this.color.alpha,this.ctx.fillStyle=this.color.hex,this.ctx.strokeStyle=this.color.hex,this.ctx.lineWidth=1,this.#c(),this.option.particles.drawLines&&this.#p(),this.option.debug.drawGrid&&this.#d(this.option.particles.connectDist),this.option.debug.drawIndexes&&this.#u()}#g(){this.isAnimating&&(requestAnimationFrame(()=>this.#g()),this.#f(),this.#i())}start({auto:t=!1}={}){return this.isAnimating||t&&!this.enableAnimating||(this.enableAnimating=!0,this.isAnimating=!0,this.updateCanvasRect(),requestAnimationFrame(()=>this.#g())),!this.canvas.inViewbox&&this.option.animation.startOnEnter&&(this.isAnimating=!1),this}stop({auto:t=!1,clear:i=!0}={}){return t||(this.enableAnimating=!1),this.isAnimating=!1,!1!==i&&this.ctx.clearRect(0,0,this.canvas.rect.width,this.canvas.rect.height),!0}destroy(){this.stop(),CanvasParticles.instances.delete(this),CanvasParticles.canvasIntersectionObserver.unobserve(this.canvas),CanvasParticles.canvasResizeObserver.unobserve(this.canvas),this.canvas?.remove(),Object.keys(this).forEach(t=>delete this[t])}set options(i){const e=t;this.option={background:i.background??!1,animation:{startOnEnter:!!(i.animation?.startOnEnter??1),stopOnLeave:!!(i.animation?.stopOnLeave??1)},mouse:{interactionType:~~e("mouse.interactionType",i.mouse?.interactionType,CanvasParticles.interactionType.MOVE,{min:0,max:2}),connectDist:1,distRatio:e("mouse.distRatio",i.mouse?.distRatio,2/3,{min:0})},particles:{generationType:~~e("particles.generationType",i.particles?.generationType,CanvasParticles.generationType.MATCH,{min:0,max:2}),drawLines:!!(i.particles?.drawLines??1),color:i.particles?.color??"black",ppm:~~e("particles.ppm",i.particles?.ppm,100),max:Math.round(e("particles.max",i.particles?.max,1/0,{min:0})),maxWork:Math.round(e("particles.maxWork",i.particles?.maxWork,1/0,{min:0})),connectDist:~~e("particles.connectDistance",i.particles?.connectDistance,150,{min:1}),relSpeed:e("particles.relSpeed",i.particles?.relSpeed,1,{min:0}),relSize:e("particles.relSize",i.particles?.relSize,1,{min:0}),rotationSpeed:e("particles.rotationSpeed",i.particles?.rotationSpeed,2,{min:0})/100},gravity:{repulsive:e("gravity.repulsive",i.gravity?.repulsive,0,{min:0}),pulling:e("gravity.pulling",i.gravity?.pulling,0,{min:0}),friction:e("gravity.friction",i.gravity?.friction,.8,{min:0,max:1}),maxVelocity:e("gravity.maxVelocity",i.gravity?.maxVelocity,1/0,{min:0})},debug:{drawGrid:!!i.debug?.drawGrid,drawIndexes:!!i.debug?.drawIndexes}},this.setBackground(this.option.background),this.setMouseConnectDistMult(i.mouse?.connectDistMult),this.setParticleColor(this.option.particles.color)}get options(){return this.option}setBackground(t){if(t){if("string"!=typeof t)throw new TypeError("background is not a string");this.canvas.style.background=this.option.background=t}}setMouseConnectDistMult(i){const e=t("mouse.connectDistMult",i,2/3,{min:0});this.option.mouse.connectDist=this.option.particles.connectDist*e}setParticleColor(t){if(this.ctx.fillStyle=t,"#"===String(this.ctx.fillStyle)[0])this.color={hex:String(this.ctx.fillStyle),alpha:1};else{let t=String(this.ctx.fillStyle).split(",").at(-1);t=t?.slice(1,-1)??"1",this.ctx.fillStyle=String(this.ctx.fillStyle).split(",").slice(0,-1).join(",")+", 1)",this.color={hex:String(this.ctx.fillStyle),alpha:isNaN(+t)?1:+t}}}}return window.addEventListener("mousemove",t=>{for(const i of CanvasParticles.instances)i.handleMouseMove(t)},{passive:!0}),window.addEventListener("scroll",()=>{for(const t of CanvasParticles.instances)t.handleScroll()},{passive:!0}),CanvasParticles});
package/package.json CHANGED
@@ -1,76 +1,76 @@
1
- {
2
- "name": "canvasparticles-js",
3
- "version": "4.5.1",
4
- "description": "In an HTML canvas, a bunch of interactive particles connected with lines when they approach each other.",
5
- "author": "Khoeckman",
6
- "license": "MIT",
7
- "type": "module",
8
- "main": "dist/index.umd.js",
9
- "module": "dist/index.mjs",
10
- "types": "dist/index.d.ts",
11
- "exports": {
12
- ".": {
13
- "types": "./dist/index.d.ts",
14
- "require": "./dist/index.cjs",
15
- "import": "./dist/index.mjs",
16
- "default": "./dist/index.umd.js"
17
- }
18
- },
19
- "files": [
20
- "src/",
21
- "dist/"
22
- ],
23
- "scripts": {
24
- "test": "exit 0",
25
- "build": "rollup -c",
26
- "prepack": "npm run build"
27
- },
28
- "devDependencies": {
29
- "@rollup/plugin-replace": "^6.0.3",
30
- "@rollup/plugin-terser": "^0.4.4",
31
- "@rollup/plugin-typescript": "^12.3.0",
32
- "@types/node": "^24.10.7",
33
- "prettier": "^3.7.4",
34
- "rollup": "^4.55.1",
35
- "rollup-plugin-delete": "^3.0.2",
36
- "tslib": "^2.8.1",
37
- "typescript": "^5.9.3"
38
- },
39
- "publishConfig": {
40
- "access": "public"
41
- },
42
- "homepage": "https://khoeckman.github.io/canvasparticles-js/",
43
- "repository": {
44
- "type": "git",
45
- "url": "git+https://github.com/Khoeckman/canvasparticles-js.git"
46
- },
47
- "bugs": {
48
- "url": "https://github.com/Khoeckman/canvasparticles-js/issues"
49
- },
50
- "keywords": [
51
- "front-end",
52
- "frontend",
53
- "canvas",
54
- "particle",
55
- "particles",
56
- "jsparticles",
57
- "js-particles",
58
- "particles.js",
59
- "particles-js",
60
- "xparticles",
61
- "background",
62
- "animation",
63
- "animated",
64
- "interactive",
65
- "interaction",
66
- "web",
67
- "webdesign",
68
- "web-design",
69
- "javascript",
70
- "js",
71
- "ecmascript",
72
- "module",
73
- "html5",
74
- "html"
75
- ]
76
- }
1
+ {
2
+ "name": "canvasparticles-js",
3
+ "version": "4.5.4",
4
+ "description": "In an HTML canvas, a bunch of interactive particles connected with lines when they approach each other.",
5
+ "author": "Khoeckman",
6
+ "license": "MIT",
7
+ "type": "module",
8
+ "main": "dist/index.umd.js",
9
+ "module": "dist/index.mjs",
10
+ "types": "dist/index.d.ts",
11
+ "exports": {
12
+ ".": {
13
+ "types": "./dist/index.d.ts",
14
+ "require": "./dist/index.cjs",
15
+ "import": "./dist/index.mjs",
16
+ "default": "./dist/index.umd.js"
17
+ }
18
+ },
19
+ "files": [
20
+ "src/",
21
+ "dist/"
22
+ ],
23
+ "scripts": {
24
+ "test": "exit 0",
25
+ "build": "rollup -c",
26
+ "prepack": "npm run build"
27
+ },
28
+ "devDependencies": {
29
+ "@rollup/plugin-replace": "^6.0.3",
30
+ "@rollup/plugin-terser": "^1.0.0",
31
+ "@rollup/plugin-typescript": "^12.3.0",
32
+ "@types/node": "^25.5.0",
33
+ "prettier": "^3.8.1",
34
+ "rollup": "^4.59.0",
35
+ "rollup-plugin-delete": "^3.0.2",
36
+ "tslib": "^2.8.1",
37
+ "typescript": "^5.9.3"
38
+ },
39
+ "publishConfig": {
40
+ "access": "public"
41
+ },
42
+ "homepage": "https://khoeckman.github.io/canvasparticles-js/",
43
+ "repository": {
44
+ "type": "git",
45
+ "url": "git+https://github.com/Khoeckman/canvasparticles-js.git"
46
+ },
47
+ "bugs": {
48
+ "url": "https://github.com/Khoeckman/canvasparticles-js/issues"
49
+ },
50
+ "keywords": [
51
+ "front-end",
52
+ "frontend",
53
+ "canvas",
54
+ "particle",
55
+ "particles",
56
+ "jsparticles",
57
+ "js-particles",
58
+ "particles.js",
59
+ "particles-js",
60
+ "xparticles",
61
+ "background",
62
+ "animation",
63
+ "animated",
64
+ "interactive",
65
+ "interaction",
66
+ "web",
67
+ "webdesign",
68
+ "web-design",
69
+ "javascript",
70
+ "js",
71
+ "ecmascript",
72
+ "module",
73
+ "html5",
74
+ "html"
75
+ ]
76
+ }
package/src/index.ts CHANGED
@@ -23,7 +23,7 @@ function Mulberry32(seed: number) {
23
23
  }
24
24
  }
25
25
 
26
- // Mulberry32 is ±392% faster than Math.random()
26
+ // Mulberry32 is x4 faster than Math.random()
27
27
  // Benchmark: https://jsbm.dev/muLCWR9RJCbmy
28
28
  // Spectral test: /demo/mulberry32.html
29
29
  const prng = Mulberry32(Math.random() * 4294967296).next
@@ -178,11 +178,13 @@ export default class CanvasParticles {
178
178
 
179
179
  /** Resize the canvas and update particles accordingly */
180
180
  #resizeCanvas(dpr = window.devicePixelRatio || 1) {
181
+ if (dpr < 1) dpr = 1
182
+
181
183
  const width = (this.canvas.width = this.canvas.rect.width * dpr)
182
184
  const height = (this.canvas.height = this.canvas.rect.height * dpr)
183
185
 
184
186
  // Must be set every time width or height changes because scale is removed
185
- if (dpr !== 1) this.ctx.scale(dpr, dpr)
187
+ if (dpr > 1) this.ctx.scale(dpr, dpr)
186
188
 
187
189
  // Hide the mouse when resizing because it must be outside the viewport to do so
188
190
  this.mouseX = Infinity
@@ -500,12 +502,13 @@ export default class CanvasParticles {
500
502
  /** Draw the particles on the canvas */
501
503
  #renderParticles() {
502
504
  const ctx = this.ctx
505
+ const dpr = window.devicePixelRatio || 1
503
506
 
504
507
  for (const p of this.particles) {
505
508
  if (!p.isVisible) continue
506
509
 
507
510
  // Draw particles smaller than 1px as a square instead of a circle for performance
508
- if (p.size > 1) {
511
+ if (p.size > 1 / dpr) {
509
512
  // Draw circle
510
513
  ctx.beginPath()
511
514
  ctx.arc(p.x, p.y, p.size, 0, TWO_PI)
@@ -774,7 +777,7 @@ export default class CanvasParticles {
774
777
 
775
778
  /** Clear the canvas and render the particles and their connections onto the canvas */
776
779
  #render() {
777
- this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height)
780
+ this.ctx.clearRect(0, 0, this.canvas.rect.width, this.canvas.rect.height)
778
781
 
779
782
  this.ctx.globalAlpha = this.color.alpha
780
783
  this.ctx.fillStyle = this.color.hex
@@ -816,7 +819,7 @@ export default class CanvasParticles {
816
819
  stop({ auto = false, clear = true }: { auto?: boolean; clear?: boolean } = {}): boolean {
817
820
  if (!auto) this.enableAnimating = false
818
821
  this.isAnimating = false
819
- if (clear !== false) this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height)
822
+ if (clear !== false) this.ctx.clearRect(0, 0, this.canvas.rect.width, this.canvas.rect.height)
820
823
  return true
821
824
  }
822
825