canvasparticles-js 4.4.3 → 4.4.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 +31 -23
- package/dist/index.cjs +23 -9
- package/dist/index.d.ts +1 -1
- package/dist/index.mjs +23 -9
- package/dist/index.umd.js +1 -1
- package/dist/types/options.d.ts +1 -0
- package/package.json +1 -1
- package/src/index.ts +21 -8
- package/src/types/options.ts +1 -0
package/README.md
CHANGED
|
@@ -175,8 +175,7 @@ Play around with these values in the [Sandbox](https://khoeckman.github.io/canva
|
|
|
175
175
|
|
|
176
176
|
### Options Object
|
|
177
177
|
|
|
178
|
-
The default value will be used when an option is assigned an invalid value
|
|
179
|
-
Your screen resolution and refresh rate will directly impact perfomance!
|
|
178
|
+
The default value will be used when an option is assigned an invalid value.
|
|
180
179
|
|
|
181
180
|
### Root options
|
|
182
181
|
|
|
@@ -188,6 +187,8 @@ Your screen resolution and refresh rate will directly impact perfomance!
|
|
|
188
187
|
|
|
189
188
|
### `animation`
|
|
190
189
|
|
|
190
|
+
It's best to not touch these values if it's unclear what it does.
|
|
191
|
+
|
|
191
192
|
| Option | Type | Default | Description |
|
|
192
193
|
| ------------------------ | --------- | ------- | ---------------------------------------------------- |
|
|
193
194
|
| `animation.startOnEnter` | `boolean` | `true` | Start animation when the canvas enters the viewport. |
|
|
@@ -207,24 +208,30 @@ Your screen resolution and refresh rate will directly impact perfomance!
|
|
|
207
208
|
|
|
208
209
|
- `NONE (0)` – No interaction
|
|
209
210
|
- `SHIFT (1)` – Visual displacement only
|
|
210
|
-
- `MOVE (2)` – Actual particle movement
|
|
211
|
+
- `MOVE (2)` – Actual particle movement (default)
|
|
211
212
|
|
|
212
213
|
---
|
|
213
214
|
|
|
214
215
|
### `particles`
|
|
215
216
|
|
|
216
|
-
| Option | Type | Default | Description
|
|
217
|
-
| --------------------------- | ------------- | ---------- |
|
|
218
|
-
| `particles.generationType` | `0 \| 1 \| 2` | `false` | Auto-generate particles on initialization and when the canvas resizes. `
|
|
219
|
-
| `particles.color` | `string` | `'black'` | Particle and connection color. Any CSS color format.
|
|
220
|
-
| `particles.ppm` | `integer` | `100` | Particles per million pixels.
|
|
221
|
-
| `particles.max` | `integer` | `Infinity` | Maximum number of particles allowed.
|
|
222
|
-
| `particles.maxWork` | `integer` | `Infinity` | Maximum total connection length per particle. Lower values stabilize performance but may flicker.
|
|
223
|
-
| `particles.connectDistance` | `integer` | `150` | Maximum distance for particle connections (px).
|
|
224
|
-
| `particles.relSpeed` | `float` | `1` | Relative particle speed multiplier.
|
|
225
|
-
| `particles.relSize` | `float` | `1` | Relative particle size multiplier.
|
|
226
|
-
| `particles.rotationSpeed` | `float` | `2` | Direction change speed.
|
|
227
|
-
| `particles.drawLines` | `boolean` | `true` | Whether to draw lines between particles.
|
|
217
|
+
| Option | Type | Default | Description |
|
|
218
|
+
| --------------------------- | ------------- | ---------- | ------------------------------------------------------------------------------------------------- |
|
|
219
|
+
| `particles.generationType` | `0 \| 1 \| 2` | `false` | Auto-generate particles on initialization and when the canvas resizes. `1 = OFF`, `2 = MATCH` |
|
|
220
|
+
| `particles.color` | `string` | `'black'` | Particle and connection color. Any CSS color format. |
|
|
221
|
+
| `particles.ppm` | `integer` | `100` | Particles per million pixels. |
|
|
222
|
+
| `particles.max` | `integer` | `Infinity` | Maximum number of particles allowed. |
|
|
223
|
+
| `particles.maxWork` | `integer` | `Infinity` | Maximum total connection length per particle. Lower values stabilize performance but may flicker. |
|
|
224
|
+
| `particles.connectDistance` | `integer` | `150` | Maximum distance for particle connections (px). |
|
|
225
|
+
| `particles.relSpeed` | `float` | `1` | Relative particle speed multiplier. |
|
|
226
|
+
| `particles.relSize` | `float` | `1` | Relative particle size multiplier. |
|
|
227
|
+
| `particles.rotationSpeed` | `float` | `2` | Direction change speed. |
|
|
228
|
+
| `particles.drawLines` | `boolean` | `true` | Whether to draw lines between particles. |
|
|
229
|
+
|
|
230
|
+
**Generation types** (enum)
|
|
231
|
+
|
|
232
|
+
- `OFF (0)` – Never auto-generate particles
|
|
233
|
+
- `NEW (1)` – Generate all particles from scratch
|
|
234
|
+
- `MATCH (2)` – Add or remove some particles to match the new count (default)
|
|
228
235
|
|
|
229
236
|
---
|
|
230
237
|
|
|
@@ -232,11 +239,12 @@ Your screen resolution and refresh rate will directly impact perfomance!
|
|
|
232
239
|
|
|
233
240
|
Enabling gravity (`repulsive` or `pulling` > 0) performs an extra **O(n²)** gravity computations per frame.
|
|
234
241
|
|
|
235
|
-
| Option
|
|
236
|
-
|
|
|
237
|
-
| `gravity.repulsive`
|
|
238
|
-
| `gravity.pulling`
|
|
239
|
-
| `gravity.friction`
|
|
242
|
+
| Option | Type | Default | Description |
|
|
243
|
+
| --------------------------- | --------- | ------- | --------------------------------------------------------------------------------------------- |
|
|
244
|
+
| `gravity.repulsive` | `float` | `0` | Repulsive force between particles. |
|
|
245
|
+
| `gravity.pulling` | `float` | `0` | Attractive force between particles. Requires sufficient repulsion to avoid clustering. |
|
|
246
|
+
| `gravity.friction` | `float` | `0.8` | Damping factor applied to gravitational velocity each update (`0.0 – 1.0`). |
|
|
247
|
+
| `gravity.preventExplosions` | `boolean` | `false` | Clamp the maximum velocity so particles will not explode outward under heavy pulling gravity. |
|
|
240
248
|
|
|
241
249
|
---
|
|
242
250
|
|
|
@@ -342,13 +350,13 @@ instance.options = { ... }
|
|
|
342
350
|
createParticle(posX?: number, posY?: number, dir?: number, speed?: number, size?: number)
|
|
343
351
|
```
|
|
344
352
|
|
|
345
|
-
By default `particles.ppm` and `particles.max` are used to auto-generate random particles.
|
|
353
|
+
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).
|
|
346
354
|
|
|
347
355
|
```js
|
|
348
356
|
const canvas = '#my-canvas'
|
|
349
357
|
const options = {
|
|
350
358
|
particles: {
|
|
351
|
-
|
|
359
|
+
generationType: CanvasParticles.generationType.OFF, // = 0
|
|
352
360
|
rotationSpeed: 0,
|
|
353
361
|
},
|
|
354
362
|
}
|
|
@@ -393,7 +401,7 @@ instance.newParticles({ keepAuto: true, keepManual: false })
|
|
|
393
401
|
const options = {
|
|
394
402
|
background: 'hsl(125, 42%, 35%)',
|
|
395
403
|
mouse: {
|
|
396
|
-
interactionType: CanvasParticles.interactionType.
|
|
404
|
+
interactionType: CanvasParticles.interactionType.SHIFT, // = 1
|
|
397
405
|
},
|
|
398
406
|
particles: {
|
|
399
407
|
color: 'rgba(150, 255, 105, 0.95)',
|
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.4.
|
|
24
|
+
static version = "4.4.4";
|
|
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 */
|
|
@@ -32,9 +32,9 @@ class CanvasParticles {
|
|
|
32
32
|
});
|
|
33
33
|
/** Defines how the particles are auto-generated */
|
|
34
34
|
static generationType = Object.freeze({
|
|
35
|
-
|
|
36
|
-
NEW: 1, // Generate particles from scratch
|
|
37
|
-
MATCH: 2, // Add or remove particles to match new count (default)
|
|
35
|
+
OFF: 0, // Never auto-generate particles
|
|
36
|
+
NEW: 1, // Generate all particles from scratch
|
|
37
|
+
MATCH: 2, // Add or remove some particles to match the new count (default)
|
|
38
38
|
});
|
|
39
39
|
/** Observes canvas elements entering or leaving the viewport to start/stop animation */
|
|
40
40
|
static canvasIntersectionObserver = new IntersectionObserver((entries) => {
|
|
@@ -169,7 +169,7 @@ class CanvasParticles {
|
|
|
169
169
|
this.offX = (width - this.width) / 2;
|
|
170
170
|
this.offY = (height - this.height) / 2;
|
|
171
171
|
const generationType = this.option.particles.generationType;
|
|
172
|
-
if (generationType !== CanvasParticles.generationType.
|
|
172
|
+
if (generationType !== CanvasParticles.generationType.OFF) {
|
|
173
173
|
if (generationType === CanvasParticles.generationType.NEW || this.particles.length === 0)
|
|
174
174
|
this.newParticles();
|
|
175
175
|
else if (generationType === CanvasParticles.generationType.MATCH)
|
|
@@ -343,12 +343,13 @@ class CanvasParticles {
|
|
|
343
343
|
const offY = this.offY;
|
|
344
344
|
const mouseX = this.mouseX;
|
|
345
345
|
const mouseY = this.mouseY;
|
|
346
|
-
const rotationSpeed = this.option.particles.rotationSpeed * step;
|
|
347
|
-
const friction = this.option.gravity.friction;
|
|
348
|
-
const mouseConnectDist = this.option.mouse.connectDist;
|
|
349
|
-
const mouseDistRatio = this.option.mouse.distRatio;
|
|
350
346
|
const isMouseInteractionTypeNone = this.option.mouse.interactionType === CanvasParticles.interactionType.NONE;
|
|
351
347
|
const isMouseInteractionTypeMove = this.option.mouse.interactionType === CanvasParticles.interactionType.MOVE;
|
|
348
|
+
const mouseConnectDist = this.option.mouse.connectDist;
|
|
349
|
+
const mouseDistRatio = this.option.mouse.distRatio;
|
|
350
|
+
const rotationSpeed = this.option.particles.rotationSpeed * step;
|
|
351
|
+
const friction = this.option.gravity.friction;
|
|
352
|
+
const preventExplosions = this.option.gravity.preventExplosions;
|
|
352
353
|
const easing = 1 - Math.pow(3 / 4, step);
|
|
353
354
|
for (const p of this.particles) {
|
|
354
355
|
p.dir += 2 * (Math.random() - 0.5) * rotationSpeed * step;
|
|
@@ -356,6 +357,18 @@ class CanvasParticles {
|
|
|
356
357
|
// Constant velocity
|
|
357
358
|
const movX = Math.sin(p.dir) * p.speed;
|
|
358
359
|
const movY = Math.cos(p.dir) * p.speed;
|
|
360
|
+
// Maximum velocity
|
|
361
|
+
if (preventExplosions) {
|
|
362
|
+
const maxVel = Math.max(p.speed, friction) * 2;
|
|
363
|
+
if (p.velX > maxVel)
|
|
364
|
+
p.velX = maxVel;
|
|
365
|
+
if (p.velX < -maxVel)
|
|
366
|
+
p.velX = -maxVel;
|
|
367
|
+
if (p.velY > maxVel)
|
|
368
|
+
p.velY = maxVel;
|
|
369
|
+
if (p.velY < -maxVel)
|
|
370
|
+
p.velY = -maxVel;
|
|
371
|
+
}
|
|
359
372
|
// Apply velocities
|
|
360
373
|
p.posX += (movX + p.velX) * step;
|
|
361
374
|
p.posY += (movY + p.velY) * step;
|
|
@@ -761,6 +774,7 @@ class CanvasParticles {
|
|
|
761
774
|
repulsive: pno('gravity.repulsive', options.gravity?.repulsive, 0, { min: 0 }),
|
|
762
775
|
pulling: pno('gravity.pulling', options.gravity?.pulling, 0, { min: 0 }),
|
|
763
776
|
friction: pno('gravity.friction', options.gravity?.friction, 0.8, { min: 0, max: 1 }),
|
|
777
|
+
preventExplosions: !!options.gravity?.preventExplosions,
|
|
764
778
|
},
|
|
765
779
|
debug: {
|
|
766
780
|
drawGrid: !!options.debug?.drawGrid,
|
package/dist/index.d.ts
CHANGED
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.4.
|
|
22
|
+
static version = "4.4.4";
|
|
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 */
|
|
@@ -30,9 +30,9 @@ class CanvasParticles {
|
|
|
30
30
|
});
|
|
31
31
|
/** Defines how the particles are auto-generated */
|
|
32
32
|
static generationType = Object.freeze({
|
|
33
|
-
|
|
34
|
-
NEW: 1, // Generate particles from scratch
|
|
35
|
-
MATCH: 2, // Add or remove particles to match new count (default)
|
|
33
|
+
OFF: 0, // Never auto-generate particles
|
|
34
|
+
NEW: 1, // Generate all particles from scratch
|
|
35
|
+
MATCH: 2, // Add or remove some particles to match the new count (default)
|
|
36
36
|
});
|
|
37
37
|
/** Observes canvas elements entering or leaving the viewport to start/stop animation */
|
|
38
38
|
static canvasIntersectionObserver = new IntersectionObserver((entries) => {
|
|
@@ -167,7 +167,7 @@ class CanvasParticles {
|
|
|
167
167
|
this.offX = (width - this.width) / 2;
|
|
168
168
|
this.offY = (height - this.height) / 2;
|
|
169
169
|
const generationType = this.option.particles.generationType;
|
|
170
|
-
if (generationType !== CanvasParticles.generationType.
|
|
170
|
+
if (generationType !== CanvasParticles.generationType.OFF) {
|
|
171
171
|
if (generationType === CanvasParticles.generationType.NEW || this.particles.length === 0)
|
|
172
172
|
this.newParticles();
|
|
173
173
|
else if (generationType === CanvasParticles.generationType.MATCH)
|
|
@@ -341,12 +341,13 @@ class CanvasParticles {
|
|
|
341
341
|
const offY = this.offY;
|
|
342
342
|
const mouseX = this.mouseX;
|
|
343
343
|
const mouseY = this.mouseY;
|
|
344
|
-
const rotationSpeed = this.option.particles.rotationSpeed * step;
|
|
345
|
-
const friction = this.option.gravity.friction;
|
|
346
|
-
const mouseConnectDist = this.option.mouse.connectDist;
|
|
347
|
-
const mouseDistRatio = this.option.mouse.distRatio;
|
|
348
344
|
const isMouseInteractionTypeNone = this.option.mouse.interactionType === CanvasParticles.interactionType.NONE;
|
|
349
345
|
const isMouseInteractionTypeMove = this.option.mouse.interactionType === CanvasParticles.interactionType.MOVE;
|
|
346
|
+
const mouseConnectDist = this.option.mouse.connectDist;
|
|
347
|
+
const mouseDistRatio = this.option.mouse.distRatio;
|
|
348
|
+
const rotationSpeed = this.option.particles.rotationSpeed * step;
|
|
349
|
+
const friction = this.option.gravity.friction;
|
|
350
|
+
const preventExplosions = this.option.gravity.preventExplosions;
|
|
350
351
|
const easing = 1 - Math.pow(3 / 4, step);
|
|
351
352
|
for (const p of this.particles) {
|
|
352
353
|
p.dir += 2 * (Math.random() - 0.5) * rotationSpeed * step;
|
|
@@ -354,6 +355,18 @@ class CanvasParticles {
|
|
|
354
355
|
// Constant velocity
|
|
355
356
|
const movX = Math.sin(p.dir) * p.speed;
|
|
356
357
|
const movY = Math.cos(p.dir) * p.speed;
|
|
358
|
+
// Maximum velocity
|
|
359
|
+
if (preventExplosions) {
|
|
360
|
+
const maxVel = Math.max(p.speed, friction) * 2;
|
|
361
|
+
if (p.velX > maxVel)
|
|
362
|
+
p.velX = maxVel;
|
|
363
|
+
if (p.velX < -maxVel)
|
|
364
|
+
p.velX = -maxVel;
|
|
365
|
+
if (p.velY > maxVel)
|
|
366
|
+
p.velY = maxVel;
|
|
367
|
+
if (p.velY < -maxVel)
|
|
368
|
+
p.velY = -maxVel;
|
|
369
|
+
}
|
|
357
370
|
// Apply velocities
|
|
358
371
|
p.posX += (movX + p.velX) * step;
|
|
359
372
|
p.posY += (movY + p.velY) * step;
|
|
@@ -759,6 +772,7 @@ class CanvasParticles {
|
|
|
759
772
|
repulsive: pno('gravity.repulsive', options.gravity?.repulsive, 0, { min: 0 }),
|
|
760
773
|
pulling: pno('gravity.pulling', options.gravity?.pulling, 0, { min: 0 }),
|
|
761
774
|
friction: pno('gravity.friction', options.gravity?.friction, 0.8, { min: 0, max: 1 }),
|
|
775
|
+
preventExplosions: !!options.gravity?.preventExplosions,
|
|
762
776
|
},
|
|
763
777
|
debug: {
|
|
764
778
|
drawGrid: !!options.debug?.drawGrid,
|
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.4.3";static MAX_DT=20;static BASE_DT=1e3/60;static interactionType=Object.freeze({NONE:0,SHIFT:1,MOVE:2});static generationType=Object.freeze({MANUAL: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)=>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.MANUAL&&(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.particles.rotationSpeed*i,h=this.option.gravity.friction,p=this.option.mouse.connectDist,d=this.option.mouse.distRatio,u=this.option.mouse.interactionType===e.interactionType.NONE,f=this.option.mouse.interactionType===e.interactionType.MOVE,g=1-Math.pow(3/4,i);for(const e of this.particles){e.dir+=2*(Math.random()-.5)*l*i,e.dir%=t;const m=Math.sin(e.dir)*e.speed,v=Math.cos(e.dir)*e.speed;e.posX+=(m+e.velX)*i,e.posY+=(v+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(h,i),e.velY*=Math.pow(h,i);const x=e.posX+o-r,y=e.posY+a-c;if(!u){const t=p/Math.hypot(x,y);d<t?(e.offX+=(t*x-x-e.offX)*g,e.offY+=(t*y-y-e.offY)*g):(e.offX-=e.offX*g,e.offY-=e.offY*g)}e.x=e.posX+e.offX,e.y=e.posY+e.offY,f&&(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})},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}}}(Math.random()*2**32).next;class e{static version="4.4.4";static MAX_DT=20;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)=>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,v=-h*f;i.velX+=g,i.velY+=v,t.velX-=g,t.velY-=v}}}#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.preventExplosions,v=1-Math.pow(3/4,i);for(const e of this.particles){e.dir+=2*(Math.random()-.5)*u*i,e.dir%=t;const m=Math.sin(e.dir)*e.speed,x=Math.cos(e.dir)*e.speed;if(g){const t=2*Math.max(e.speed,f);e.velX>t&&(e.velX=t),e.velX<-t&&(e.velX=-t),e.velY>t&&(e.velY=t),e.velY<-t&&(e.velY=-t)}e.posX+=(m+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)*v,e.offY+=(t*M-M-e.offY)*v):(e.offX-=e.offX*v,e.offY-=e.offY*v)}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,v=!0;function m(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,v=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))&&(m(n.x,n.y,i.x,i.y),!v))break}}function y(i,s){for(const n of i){const i=t[n];if((l||e.#l(s,i))&&(m(s.x,s.y,i.x,i.y),!v))break}}for(let e=0;e<i;e++){g=0,v=!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),v&&((s=f.get(l+c))&&y(s,n),v&&((s=f.get(l+c+1))&&y(s,n),v&&((s=f.get(l+c-1))&&y(s,n),v)))){if(o>=0&&a>=0&&o<c-2&&(s=f.get(l))&&x(s||[],e,n),++e>=i)break;if(g=0,v=!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),v&&((s=f.get(l+c-1))&&y(s,n),v&&((s=f.get(l+1))&&y(s,n),v&&((s=f.get(l+c))&&y(s,n),v)))){if(o>=0&&a>=0&&o<c-2&&(s=f.get(l))&&x(s||[],e,n),++e>=i)break;g=0,v=!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),v&&((s=f.get(l+1))&&y(s,n),v&&(o>=0&&a>=0&&o<c-2&&(s=f.get(l))&&x(s||[],e,n),v&&((s=f.get(l+c-1))&&y(s,n),v&&(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}),preventExplosions:!!t.gravity?.preventExplosions},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/dist/types/options.d.ts
CHANGED
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -43,9 +43,9 @@ export default class CanvasParticles {
|
|
|
43
43
|
|
|
44
44
|
/** Defines how the particles are auto-generated */
|
|
45
45
|
static readonly generationType = Object.freeze({
|
|
46
|
-
|
|
47
|
-
NEW: 1, // Generate particles from scratch
|
|
48
|
-
MATCH: 2, // Add or remove particles to match new count (default)
|
|
46
|
+
OFF: 0, // Never auto-generate particles
|
|
47
|
+
NEW: 1, // Generate all particles from scratch
|
|
48
|
+
MATCH: 2, // Add or remove some particles to match the new count (default)
|
|
49
49
|
})
|
|
50
50
|
|
|
51
51
|
/** Observes canvas elements entering or leaving the viewport to start/stop animation */
|
|
@@ -211,7 +211,7 @@ export default class CanvasParticles {
|
|
|
211
211
|
|
|
212
212
|
const generationType = this.option.particles.generationType
|
|
213
213
|
|
|
214
|
-
if (generationType !== CanvasParticles.generationType.
|
|
214
|
+
if (generationType !== CanvasParticles.generationType.OFF) {
|
|
215
215
|
if (generationType === CanvasParticles.generationType.NEW || this.particles.length === 0) this.newParticles()
|
|
216
216
|
else if (generationType === CanvasParticles.generationType.MATCH) this.matchParticleCount({ updateBounds: true })
|
|
217
217
|
}
|
|
@@ -414,12 +414,13 @@ export default class CanvasParticles {
|
|
|
414
414
|
const offY = this.offY
|
|
415
415
|
const mouseX = this.mouseX
|
|
416
416
|
const mouseY = this.mouseY
|
|
417
|
-
const rotationSpeed = this.option.particles.rotationSpeed * step
|
|
418
|
-
const friction = this.option.gravity.friction
|
|
419
|
-
const mouseConnectDist = this.option.mouse.connectDist
|
|
420
|
-
const mouseDistRatio = this.option.mouse.distRatio
|
|
421
417
|
const isMouseInteractionTypeNone = this.option.mouse.interactionType === CanvasParticles.interactionType.NONE
|
|
422
418
|
const isMouseInteractionTypeMove = this.option.mouse.interactionType === CanvasParticles.interactionType.MOVE
|
|
419
|
+
const mouseConnectDist = this.option.mouse.connectDist
|
|
420
|
+
const mouseDistRatio = this.option.mouse.distRatio
|
|
421
|
+
const rotationSpeed = this.option.particles.rotationSpeed * step
|
|
422
|
+
const friction = this.option.gravity.friction
|
|
423
|
+
const preventExplosions = this.option.gravity.preventExplosions
|
|
423
424
|
const easing = 1 - Math.pow(3 / 4, step)
|
|
424
425
|
|
|
425
426
|
for (const p of this.particles) {
|
|
@@ -430,6 +431,17 @@ export default class CanvasParticles {
|
|
|
430
431
|
const movX = Math.sin(p.dir) * p.speed
|
|
431
432
|
const movY = Math.cos(p.dir) * p.speed
|
|
432
433
|
|
|
434
|
+
// Maximum velocity
|
|
435
|
+
if (preventExplosions) {
|
|
436
|
+
const maxVel = Math.max(p.speed, friction) * 2
|
|
437
|
+
|
|
438
|
+
if (p.velX > maxVel) p.velX = maxVel
|
|
439
|
+
if (p.velX < -maxVel) p.velX = -maxVel
|
|
440
|
+
|
|
441
|
+
if (p.velY > maxVel) p.velY = maxVel
|
|
442
|
+
if (p.velY < -maxVel) p.velY = -maxVel
|
|
443
|
+
}
|
|
444
|
+
|
|
433
445
|
// Apply velocities
|
|
434
446
|
p.posX += (movX + p.velX) * step
|
|
435
447
|
p.posY += (movY + p.velY) * step
|
|
@@ -878,6 +890,7 @@ export default class CanvasParticles {
|
|
|
878
890
|
repulsive: pno('gravity.repulsive', options.gravity?.repulsive, 0, { min: 0 }),
|
|
879
891
|
pulling: pno('gravity.pulling', options.gravity?.pulling, 0, { min: 0 }),
|
|
880
892
|
friction: pno('gravity.friction', options.gravity?.friction, 0.8, { min: 0, max: 1 }),
|
|
893
|
+
preventExplosions: !!options.gravity?.preventExplosions,
|
|
881
894
|
},
|
|
882
895
|
debug: {
|
|
883
896
|
drawGrid: !!options.debug?.drawGrid,
|