canvasparticles-js 4.1.6 → 4.2.1
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 +113 -205
- package/dist/index.cjs +26 -23
- package/dist/index.mjs +26 -23
- package/dist/index.umd.js +1 -1
- package/dist/types/options.d.ts +1 -0
- package/package.json +1 -1
- package/src/index.ts +30 -23
- package/src/types/options.ts +1 -0
package/README.md
CHANGED
|
@@ -11,19 +11,25 @@ In an HTML canvas, a bunch of floating particles connected with lines when they
|
|
|
11
11
|
Creating a fun and interactive background. Colors, interaction and gravity can be customized!
|
|
12
12
|
|
|
13
13
|
[Showcase](#showcase)<br>
|
|
14
|
+
[Import](#import)<br>
|
|
14
15
|
[Implementation](#implementation)<br>
|
|
15
|
-
[Class
|
|
16
|
+
[Class Instantiation](#class-instantiation)<br>
|
|
16
17
|
[Options](#options)<br>
|
|
17
|
-
[One
|
|
18
|
+
[One Pager Example](#one-pager-example)
|
|
19
|
+
|
|
20
|
+
---
|
|
18
21
|
|
|
19
22
|
## Showcase
|
|
20
23
|
|
|
21
24
|
If you dont like reading documentation this website is for you:<br>
|
|
25
|
+
|
|
22
26
|
[https://khoeckman.github.io/canvasparticles-js/](https://khoeckman.github.io/canvasparticles-js/)
|
|
23
27
|
|
|
24
|
-

|
|
28
|
+
[](https://khoeckman.github.io/canvasparticles-js/)
|
|
25
29
|
|
|
26
|
-
|
|
30
|
+
---
|
|
31
|
+
|
|
32
|
+
## Import
|
|
27
33
|
|
|
28
34
|
Particles will be drawn onto a `<canvas>` element.
|
|
29
35
|
|
|
@@ -31,107 +37,51 @@ Particles will be drawn onto a `<canvas>` element.
|
|
|
31
37
|
<canvas id="my-canvas"></canvas>
|
|
32
38
|
```
|
|
33
39
|
|
|
34
|
-
|
|
35
|
-
<summary><h3 style="display: inline;">Import with npm</h3></summary>
|
|
40
|
+
### npm
|
|
36
41
|
|
|
37
42
|
```bash
|
|
38
43
|
npm install canvasparticles-js
|
|
39
44
|
# or
|
|
40
45
|
pnpm add canvasparticles-js
|
|
41
|
-
# or
|
|
42
|
-
yarn add canvasparticles-js
|
|
43
|
-
```
|
|
44
|
-
|
|
45
|
-
Add a `<script>` element in the `<head>` to import _initParticles.js_.
|
|
46
|
-
|
|
47
|
-
```html
|
|
48
|
-
<head>
|
|
49
|
-
<script src="./initParticles.js" type="module"></script>
|
|
50
|
-
</head>
|
|
51
46
|
```
|
|
52
47
|
|
|
53
|
-
|
|
48
|
+
**ES Module import**
|
|
54
49
|
|
|
55
50
|
```js
|
|
56
51
|
import CanvasParticles from 'canvasparticles-js'
|
|
57
|
-
|
|
58
|
-
const selector = '#my-canvas' // Query Selector for the canvas
|
|
59
|
-
const options = { ... } // See #options
|
|
60
|
-
new CanvasParticles(selector, options).start()
|
|
61
52
|
```
|
|
62
53
|
|
|
63
|
-
|
|
54
|
+
If you don't have a bundler:
|
|
64
55
|
|
|
65
|
-
|
|
66
|
-
|
|
56
|
+
```js
|
|
57
|
+
import CanvasParticles from './node_modules/canvasparticles-js/dist/index.mjs'
|
|
58
|
+
```
|
|
67
59
|
|
|
68
|
-
|
|
60
|
+
**Global import**
|
|
69
61
|
|
|
70
62
|
```html
|
|
71
|
-
<
|
|
72
|
-
<script src="https://cdn.jsdelivr.net/npm/canvasparticles-js/dist/index.umd.js" defer></script>
|
|
73
|
-
</head>
|
|
63
|
+
<script src="./node_modules/canvasparticles-js/dist/index.umd.js" defer></script>
|
|
74
64
|
```
|
|
75
65
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
<details>
|
|
79
|
-
<summary><h3 style="display: inline;">Import raw file as ES module (click to expand)</h3></summary>
|
|
66
|
+
No import required in each JavaScript file!
|
|
80
67
|
|
|
81
|
-
|
|
82
|
-
[Same Origin Policy](https://developer.mozilla.org/en-US/docs/Web/Security/Same-origin_policy)
|
|
68
|
+
### Import with jsDelivr
|
|
83
69
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
```html
|
|
87
|
-
<head>
|
|
88
|
-
<script src="./initParticles.js" type="module"></script>
|
|
89
|
-
</head>
|
|
90
|
-
```
|
|
91
|
-
|
|
92
|
-
Inside _initParticles.js_:
|
|
70
|
+
**ES Module import**
|
|
93
71
|
|
|
94
72
|
```js
|
|
95
|
-
import CanvasParticles from '
|
|
96
|
-
|
|
97
|
-
const selector = '#my-canvas' // Query Selector for the canvas
|
|
98
|
-
const options = { ... } // See #options
|
|
99
|
-
new CanvasParticles(selector, options).start()
|
|
73
|
+
import CanvasParticles from 'https://cdn.jsdelivr.net/npm/canvasparticles-js/dist/index.min.mjs'
|
|
100
74
|
```
|
|
101
75
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
<details>
|
|
105
|
-
<summary><h3 style="display: inline;">Import raw file globally (click to expand)</h3></summary>
|
|
106
|
-
|
|
107
|
-
Add a `<script>` element in the `<head>` to import the [dist/index.umd.js](https://github.com/Khoeckman/canvasparticles-js/blob/main/dist/index.umd.js) file.<br>
|
|
108
|
-
```html
|
|
109
|
-
<head>
|
|
110
|
-
<script src="./index.umd.js" defer></script>
|
|
111
|
-
</head>
|
|
112
|
-
```
|
|
113
|
-
|
|
114
|
-
Add an inline `<script>` element at the very bottom of the `<body>`.
|
|
76
|
+
**Global import**
|
|
115
77
|
|
|
116
78
|
```html
|
|
117
|
-
<
|
|
118
|
-
...
|
|
119
|
-
|
|
120
|
-
<script>
|
|
121
|
-
const initParticles = () => {
|
|
122
|
-
const selector = '#my-canvas' // Query Selector for the canvas
|
|
123
|
-
const options = { ... } // See #options
|
|
124
|
-
new CanvasParticles(selector, options).start()
|
|
125
|
-
}
|
|
126
|
-
document.addEventListener('DOMContentLoaded', initParticles)
|
|
127
|
-
</script>
|
|
128
|
-
</body>
|
|
79
|
+
<script src="https://cdn.jsdelivr.net/npm/canvasparticles-js/dist/index.umd.min.js" defer></script>
|
|
129
80
|
```
|
|
130
81
|
|
|
131
|
-
|
|
82
|
+
---
|
|
132
83
|
|
|
133
|
-
|
|
134
|
-
<br>
|
|
84
|
+
## Implementation
|
|
135
85
|
|
|
136
86
|
### Start animating
|
|
137
87
|
|
|
@@ -164,7 +114,9 @@ particles.destroy()
|
|
|
164
114
|
delete particles // Optional
|
|
165
115
|
```
|
|
166
116
|
|
|
167
|
-
|
|
117
|
+
---
|
|
118
|
+
|
|
119
|
+
## Class Instantiation
|
|
168
120
|
|
|
169
121
|
### Valid ways to instantiate `CanvasParticles`
|
|
170
122
|
|
|
@@ -217,137 +169,87 @@ canvas = new CanvasParticles(selector).anyOtherMethod().canvas
|
|
|
217
169
|
|
|
218
170
|
## Options
|
|
219
171
|
|
|
220
|
-
|
|
221
|
-
Play around with these values
|
|
172
|
+
Options to change the particles and their behavior aswell as what happens on `MouseMove` or `Resize` events.<br>
|
|
173
|
+
Play around with these values in the [Sandbox](https://khoeckman.github.io/canvasparticles-js/#sandbox).
|
|
222
174
|
|
|
223
|
-
|
|
224
|
-
<summary><h3 style="display: inline;">Options structure (click to expand)</h3></summary>
|
|
175
|
+
### Options Object
|
|
225
176
|
|
|
226
177
|
The default value will be used when an option is assigned an invalid value.<br>
|
|
227
178
|
Your screen resolution and refresh rate will directly impact perfomance!
|
|
228
179
|
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
/** @param {float} [options.mouse.connectDistMult=2÷3] - The maximum distance for the mouse to interact with the particles.
|
|
257
|
-
* The value is multiplied by particles.connectDistance
|
|
258
|
-
* @example 0.8 connectDistMult * 150 particles.connectDistance = 120 pixels
|
|
259
|
-
*/
|
|
260
|
-
connectDistMult: 0.8,
|
|
261
|
-
|
|
262
|
-
/** @param {number} [options.mouse.distRatio=2÷3] - All particles within set radius from the mouse will be drawn to mouse.connectDistance pixels from the mouse.
|
|
263
|
-
* @example radius = 150 connectDistance / 0.4 distRatio = 375 pixels
|
|
264
|
-
* @note Keep this value above mouse.connectDistMult
|
|
265
|
-
*/
|
|
266
|
-
distRatio: 1, // recommended: 0.2 - 1
|
|
267
|
-
},
|
|
268
|
-
|
|
269
|
-
/** @param {Object} [options.particles] - Particle settings. */
|
|
270
|
-
particles: {
|
|
271
|
-
/** param {string} [options.particles.color='black'] - The color of the particles and their connections. Can be any CSS supported color format. */
|
|
272
|
-
color: '#88c8ffa0',
|
|
273
|
-
|
|
274
|
-
/** @param {number} [options.particles.ppm=100] - Particles per million (ppm).
|
|
275
|
-
* This determines how many particles are created per million pixels of the canvas.
|
|
276
|
-
* @example FHD on Chrome = 1920 width * 937 height = 1799040 pixels; 1799040 pixels * 100 ppm / 1_000_000 = 179.904 = 179 particles
|
|
277
|
-
* @important The amount of particles exponentially reduces performance.
|
|
278
|
-
* People with large screens will have a bad experience with high values.
|
|
279
|
-
* One solution is to increase particles.connectDistance and decrease this value.
|
|
280
|
-
*/
|
|
281
|
-
ppm: 100, // recommended: < 120
|
|
282
|
-
|
|
283
|
-
/** @param {number} [options.particles.max=500] - The maximum number of particles allowed. */
|
|
284
|
-
max: 200, // recommended: < 500
|
|
285
|
-
|
|
286
|
-
/** @param {number} [options.particles.maxWork=Infinity] - The maximum "work" a particle can perform before its connections are no longer drawn.
|
|
287
|
-
* @example 10 maxWork = 10 * 150 connectDistance = max 1500 pixels of lines drawn per particle
|
|
288
|
-
* @important Low values will stabilize performance at the cost of creating an ugly effect where connections may flicker.
|
|
289
|
-
*/
|
|
290
|
-
maxWork: 10,
|
|
291
|
-
|
|
292
|
-
/** @param {number} [options.particles.connectDistance=150] - The maximum distance for a connection between 2 particles.
|
|
293
|
-
* @note Heavily affects performance. */
|
|
294
|
-
connectDistance: 150,
|
|
295
|
-
|
|
296
|
-
/** @param {number} [options.particles.relSpeed=1] - The relative moving speed of the particles.
|
|
297
|
-
* The moving speed is a random value between 0.5 and 1 pixels per update multiplied by this value.
|
|
298
|
-
*/
|
|
299
|
-
relSpeed: 0.8,
|
|
300
|
-
|
|
301
|
-
/** @param {number} [options.particles.relSize=1] - The relative size of the particles.
|
|
302
|
-
* The ray is a random value between 0.5 and 2.5 pixels multiplied by this value.
|
|
303
|
-
*/
|
|
304
|
-
relSize: 1.1,
|
|
305
|
-
|
|
306
|
-
/** @param {number} [options.particles.rotationSpeed=2] - The speed at which the particles randomly changes direction.
|
|
307
|
-
* @example 1 rotationSpeed = max direction change of 0.01 radians per update
|
|
308
|
-
*/
|
|
309
|
-
rotationSpeed: 1, // recommended: < 10
|
|
310
|
-
|
|
311
|
-
/** @param {boolean} [options.particles.regenerateOnResize=false] - Create new particles when the canvas gets resized.
|
|
312
|
-
* @note If false, will instead add or remove a few particles to match particles.ppm
|
|
313
|
-
*/
|
|
314
|
-
regenerateOnResize: false,
|
|
315
|
-
},
|
|
316
|
-
|
|
317
|
-
/** @param {Object} [options.gravity] - Gravitational force settings.
|
|
318
|
-
* @important Heavily reduces performance if gravity.repulsive or gravity.pulling is not equal to 0
|
|
319
|
-
*/
|
|
320
|
-
gravity: {
|
|
321
|
-
/** @param {number} [options.gravity.repulsive=0] - The repulsive force between particles. */
|
|
322
|
-
repulsive: 2, // recommended: 0.50 - 5.00
|
|
323
|
-
|
|
324
|
-
/** @param {number} [options.gravity.pulling=0] - The attractive force pulling particles together. Works poorly if `gravity.repulsive` is too low.
|
|
325
|
-
* @note gravity.repulsive should be great enough to prevent forming a singularity.
|
|
326
|
-
*/
|
|
327
|
-
pulling: 0.0, // recommended: 0.01 - 0.10
|
|
328
|
-
|
|
329
|
-
/** @param {number} [options.gravity.friction=0.9] - The smoothness of the gravitational forces.
|
|
330
|
-
* The force gets multiplied by the fricion every update.
|
|
331
|
-
* @example force after x updates = force * friction ** x
|
|
332
|
-
*/
|
|
333
|
-
friction: 0.8, // recommended: 0.500 - 0.999
|
|
334
|
-
},
|
|
335
|
-
}
|
|
336
|
-
```
|
|
180
|
+
### Root options
|
|
181
|
+
|
|
182
|
+
| Option | Type | Default | Description |
|
|
183
|
+
| ------------ | ----------------- | ------- | ----------------------------------------------------------------------------- |
|
|
184
|
+
| `background` | `string \| false` | `false` | Canvas background. Any valid CSS `background` value. Ignored if not a string. |
|
|
185
|
+
|
|
186
|
+
---
|
|
187
|
+
|
|
188
|
+
### `animation`
|
|
189
|
+
|
|
190
|
+
| Option | Type | Default | Description |
|
|
191
|
+
| ------------------------ | --------- | ------- | ---------------------------------------------------- |
|
|
192
|
+
| `animation.startOnEnter` | `boolean` | `true` | Start animation when the canvas enters the viewport. |
|
|
193
|
+
| `animation.stopOnLeave` | `boolean` | `true` | Stop animation when the canvas leaves the viewport. |
|
|
194
|
+
|
|
195
|
+
---
|
|
196
|
+
|
|
197
|
+
### `mouse`
|
|
198
|
+
|
|
199
|
+
| Option | Type | Default | Description |
|
|
200
|
+
| ----------------------- | ------------- | ------- | ------------------------------------------------------------------------------------------------------------ |
|
|
201
|
+
| `mouse.interactionType` | `0 \| 1 \| 2` | `2` | Mouse interaction mode.<br>`0 = NONE`, `1 = SHIFT`, `2 = MOVE`. |
|
|
202
|
+
| `mouse.connectDistMult` | `float` | `2/3` | Multiplier applied to `particles.connectDistance` to compute mouse interaction distance. |
|
|
203
|
+
| `mouse.distRatio` | `float` | `2/3` | Controls how strongly particles are pulled toward the mouse. Keep equal to or above `mouse.connectDistMult`. |
|
|
204
|
+
|
|
205
|
+
**Interaction types** (enum)
|
|
337
206
|
|
|
338
|
-
|
|
207
|
+
- `NONE (0)` – No interaction
|
|
208
|
+
- `SHIFT (1)` – Visual displacement only
|
|
209
|
+
- `MOVE (2)` – Actual particle movement
|
|
210
|
+
|
|
211
|
+
---
|
|
212
|
+
|
|
213
|
+
### `particles`
|
|
214
|
+
|
|
215
|
+
| Option | Type | Default | Description |
|
|
216
|
+
| ------------------------------ | --------- | ---------- | ------------------------------------------------------------------------------------------------- |
|
|
217
|
+
| `particles.color` | `string` | `'black'` | Particle and connection color. Any CSS color format. |
|
|
218
|
+
| `particles.ppm` | `integer` | `100` | Particles per million pixels. _Heavily impacts performance_ |
|
|
219
|
+
| `particles.max` | `integer` | `Infinity` | Maximum number of particles allowed. |
|
|
220
|
+
| `particles.maxWork` | `integer` | `Infinity` | Maximum total connection length per particle. Lower values stabilize performance but may flicker. |
|
|
221
|
+
| `particles.connectDistance` | `integer` | `150` | Maximum distance for particle connections (px). _Heavily impacts performance_ |
|
|
222
|
+
| `particles.relSpeed` | `float` | `1` | Relative particle speed multiplier. |
|
|
223
|
+
| `particles.relSize` | `float` | `1` | Relative particle size multiplier. |
|
|
224
|
+
| `particles.rotationSpeed` | `float` | `2` | Direction change speed. |
|
|
225
|
+
| `particles.regenerateOnResize` | `boolean` | `false` | Regenerate all particles when the canvas resizes. |
|
|
226
|
+
| `particles.drawLines` | `boolean` | `true` | Whether to draw lines between particles. |
|
|
227
|
+
|
|
228
|
+
---
|
|
229
|
+
|
|
230
|
+
### `gravity`
|
|
231
|
+
|
|
232
|
+
Enabling gravity (`repulsive` or `pulling` > 0) performs an extra **O(n²)** gravity computations per frame.
|
|
233
|
+
|
|
234
|
+
| Option | Type | Default | Description |
|
|
235
|
+
| ------------------- | ------- | ------- | -------------------------------------------------------------------------------------- |
|
|
236
|
+
| `gravity.repulsive` | `float` | `0` | Repulsive force between particles. Strongly impacts performance. |
|
|
237
|
+
| `gravity.pulling` | `float` | `0` | Attractive force between particles. Requires sufficient repulsion to avoid clustering. |
|
|
238
|
+
| `gravity.friction` | `float` | `0.8` | Damping factor applied to gravitational velocity each update (`0.0 – 1.0`). |
|
|
239
|
+
|
|
240
|
+
---
|
|
339
241
|
|
|
340
242
|
### Update options on the fly
|
|
341
243
|
|
|
342
|
-
|
|
244
|
+
You can update every option while an instance is animating and it works great; but some options require an extra step.
|
|
343
245
|
|
|
344
|
-
#### Using the setter
|
|
246
|
+
#### Using the available setter
|
|
345
247
|
|
|
346
|
-
These options require dedicated
|
|
248
|
+
These options are the only ones that have and require a dedicated setter to ensure proper integration:
|
|
347
249
|
|
|
348
|
-
-
|
|
349
|
-
-
|
|
350
|
-
-
|
|
250
|
+
- `background`
|
|
251
|
+
- `mouse.connectDistMult`
|
|
252
|
+
- `particles.color`
|
|
351
253
|
|
|
352
254
|
```js
|
|
353
255
|
const instance = new CanvasParticles(selector, options)
|
|
@@ -360,24 +262,29 @@ instance.setParticleColor('hsl(149, 100%, 50%)')
|
|
|
360
262
|
|
|
361
263
|
#### Changing the particle count
|
|
362
264
|
|
|
363
|
-
After updating the following options, the number of particles is not automatically adjusted
|
|
265
|
+
After updating the following options, the number of particles is **not automatically adjusted**:
|
|
364
266
|
|
|
365
|
-
-
|
|
366
|
-
-
|
|
267
|
+
- `particles.ppm`
|
|
268
|
+
- `particles.max`
|
|
367
269
|
|
|
368
270
|
```js
|
|
369
271
|
// Note: the backing field is called `option` not `options`!
|
|
370
272
|
instance.option.particles.ppm = 100
|
|
371
273
|
instance.option.particles.max = 300
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
The changes are only applied when one of the following methods is called.
|
|
372
277
|
|
|
373
|
-
|
|
278
|
+
```js
|
|
374
279
|
instance.newParticles() // Remove all particles and create the correct amount of new ones
|
|
375
280
|
instance.matchParticleCount() // Add or remove some particles to match the count
|
|
376
281
|
```
|
|
377
282
|
|
|
378
283
|
#### Modifying object properties
|
|
379
284
|
|
|
380
|
-
**All** other options can be updated by modifying the `option` internal field properties, with changes taking
|
|
285
|
+
**All** other options can be updated by only modifying the `option` internal field properties, with changes taking effect immediately.
|
|
286
|
+
|
|
287
|
+
> The new option values are not validated. Assigning invalid values will lead to unexpected behavior. It is therefore recommended to use the [options setter](#updating-entire-options-object).
|
|
381
288
|
|
|
382
289
|
```js
|
|
383
290
|
// Note: the backing field is called `option` not `options`!
|
|
@@ -391,11 +298,12 @@ instance.option.gravity.repulsive = 1
|
|
|
391
298
|
To reinitialize all options, pass a new options object to the `options` setter.
|
|
392
299
|
|
|
393
300
|
```js
|
|
394
|
-
|
|
395
|
-
instance.options = options
|
|
301
|
+
instance.options = { ... }
|
|
396
302
|
```
|
|
397
303
|
|
|
398
|
-
|
|
304
|
+
---
|
|
305
|
+
|
|
306
|
+
## One Pager Example
|
|
399
307
|
|
|
400
308
|
```html
|
|
401
309
|
<!DOCTYPE html>
|
|
@@ -423,7 +331,7 @@ instance.options = options
|
|
|
423
331
|
const options = {
|
|
424
332
|
background: 'hsl(125, 42%, 35%)',
|
|
425
333
|
mouse: {
|
|
426
|
-
interactionType: CanvasParticles.interactionType.MOVE, //
|
|
334
|
+
interactionType: CanvasParticles.interactionType.MOVE, // = 2
|
|
427
335
|
},
|
|
428
336
|
particles: {
|
|
429
337
|
color: 'rgba(150, 255, 105, 0.95)',
|
package/dist/index.cjs
CHANGED
|
@@ -16,12 +16,12 @@ function Mulberry32(seed) {
|
|
|
16
16
|
},
|
|
17
17
|
};
|
|
18
18
|
}
|
|
19
|
-
// Mulberry32 is ±
|
|
19
|
+
// Mulberry32 is ±392% faster than Math.random()
|
|
20
20
|
// Benchmark: https://jsbm.dev/muLCWR9RJCbmy
|
|
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.1
|
|
24
|
+
static version = "4.2.1";
|
|
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 */
|
|
@@ -237,11 +237,12 @@ class CanvasParticles {
|
|
|
237
237
|
return;
|
|
238
238
|
const len = this.particleCount;
|
|
239
239
|
const particles = this.particles;
|
|
240
|
-
const
|
|
241
|
-
const
|
|
242
|
-
const
|
|
243
|
-
const
|
|
244
|
-
const
|
|
240
|
+
const connectDist = this.option.particles.connectDist;
|
|
241
|
+
const gravRepulsiveMult = connectDist * this.option.gravity.repulsive * step;
|
|
242
|
+
const gravPullingMult = connectDist * this.option.gravity.pulling * step;
|
|
243
|
+
const maxRepulsiveDist = connectDist / 2;
|
|
244
|
+
const maxRepulsiveDistSq = maxRepulsiveDist * maxRepulsiveDist;
|
|
245
|
+
const eps = (connectDist * connectDist) / 256;
|
|
245
246
|
for (let i = 0; i < len; i++) {
|
|
246
247
|
const particleA = particles[i];
|
|
247
248
|
for (let j = i + 1; j < len; j++) {
|
|
@@ -250,17 +251,17 @@ class CanvasParticles {
|
|
|
250
251
|
const distX = particleA.posX - particleB.posX;
|
|
251
252
|
const distY = particleA.posY - particleB.posY;
|
|
252
253
|
const distSq = distX * distX + distY * distY;
|
|
254
|
+
if (distSq >= maxRepulsiveDistSq && !isPullingEnabled)
|
|
255
|
+
continue;
|
|
253
256
|
let angle;
|
|
254
257
|
let grav;
|
|
255
258
|
let gravMult;
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
angle = Math.atan2(particleB.posY - particleA.posY, particleB.posX - particleA.posX);
|
|
259
|
-
grav = Math.pow(1 / Math.sqrt(distSq), 1.8);
|
|
259
|
+
angle = Math.atan2(-distY, -distX);
|
|
260
|
+
grav = 1 / (distSq + eps);
|
|
260
261
|
const angleX = Math.cos(angle);
|
|
261
262
|
const angleY = Math.sin(angle);
|
|
262
263
|
if (distSq < maxRepulsiveDistSq) {
|
|
263
|
-
gravMult =
|
|
264
|
+
gravMult = grav * gravRepulsiveMult;
|
|
264
265
|
const gravX = angleX * gravMult;
|
|
265
266
|
const gravY = angleY * gravMult;
|
|
266
267
|
particleA.velX -= gravX;
|
|
@@ -270,7 +271,7 @@ class CanvasParticles {
|
|
|
270
271
|
}
|
|
271
272
|
if (!isPullingEnabled)
|
|
272
273
|
continue;
|
|
273
|
-
gravMult =
|
|
274
|
+
gravMult = grav * gravPullingMult;
|
|
274
275
|
const gravX = angleX * gravMult;
|
|
275
276
|
const gravY = angleY * gravMult;
|
|
276
277
|
particleA.velX += gravX;
|
|
@@ -404,9 +405,9 @@ class CanvasParticles {
|
|
|
404
405
|
const particles = this.particles;
|
|
405
406
|
const ctx = this.ctx;
|
|
406
407
|
const maxDist = this.option.particles.connectDist;
|
|
407
|
-
const maxDistSq = maxDist
|
|
408
|
+
const maxDistSq = maxDist * maxDist;
|
|
408
409
|
const halfMaxDist = maxDist / 2;
|
|
409
|
-
const halfMaxDistSq = halfMaxDist
|
|
410
|
+
const halfMaxDistSq = halfMaxDist * halfMaxDist;
|
|
410
411
|
const drawAll = maxDist >= Math.min(this.canvas.width, this.canvas.height);
|
|
411
412
|
const maxWorkPerParticle = maxDistSq * this.option.particles.maxWork;
|
|
412
413
|
const alpha = this.color.alpha;
|
|
@@ -420,7 +421,7 @@ class CanvasParticles {
|
|
|
420
421
|
// Code in this scope runs O(n^2) times per frame!
|
|
421
422
|
const particleB = particles[j];
|
|
422
423
|
// Don't draw the line if it wouldn't be visible
|
|
423
|
-
if (!
|
|
424
|
+
if (!drawAll && !this.#isLineVisible(particleA, particleB))
|
|
424
425
|
continue;
|
|
425
426
|
const distX = particleA.x - particleB.x;
|
|
426
427
|
const distY = particleA.y - particleB.y;
|
|
@@ -465,7 +466,8 @@ class CanvasParticles {
|
|
|
465
466
|
this.ctx.strokeStyle = this.color.hex;
|
|
466
467
|
this.ctx.lineWidth = 1;
|
|
467
468
|
this.#renderParticles();
|
|
468
|
-
this
|
|
469
|
+
if (this.options.particles.drawLines)
|
|
470
|
+
this.#renderConnections();
|
|
469
471
|
}
|
|
470
472
|
/** @private Main animation loop that updates and renders the particles */
|
|
471
473
|
#animation() {
|
|
@@ -528,20 +530,21 @@ class CanvasParticles {
|
|
|
528
530
|
stopOnLeave: !!(options.animation?.stopOnLeave ?? true),
|
|
529
531
|
},
|
|
530
532
|
mouse: {
|
|
531
|
-
interactionType: pno('mouse.interactionType', options.mouse?.interactionType,
|
|
533
|
+
interactionType: ~~pno('mouse.interactionType', options.mouse?.interactionType, CanvasParticles.interactionType.MOVE, { min: 0, max: 2 }),
|
|
532
534
|
connectDistMult: pno('mouse.connectDistMult', options.mouse?.connectDistMult, 2 / 3),
|
|
533
535
|
connectDist: 1 /* post processed */,
|
|
534
536
|
distRatio: pno('mouse.distRatio', options.mouse?.distRatio, 2 / 3),
|
|
535
537
|
},
|
|
536
538
|
particles: {
|
|
537
539
|
regenerateOnResize: !!options.particles?.regenerateOnResize,
|
|
540
|
+
drawLines: !!(options.particles?.drawLines ?? true),
|
|
538
541
|
color: options.particles?.color ?? 'black',
|
|
539
|
-
ppm: pno('particles.ppm', options.particles?.ppm, 100),
|
|
540
|
-
max: pno('particles.max', options.particles?.max, Infinity),
|
|
541
|
-
maxWork: pno('particles.maxWork', options.particles?.maxWork, Infinity, { min: 0 }),
|
|
542
|
-
connectDist: pno('particles.connectDistance', options.particles?.connectDistance, 150, { min: 1 }),
|
|
542
|
+
ppm: ~~pno('particles.ppm', options.particles?.ppm, 100),
|
|
543
|
+
max: Math.round(pno('particles.max', options.particles?.max, Infinity)),
|
|
544
|
+
maxWork: Math.round(pno('particles.maxWork', options.particles?.maxWork, Infinity, { min: 0 })),
|
|
545
|
+
connectDist: ~~pno('particles.connectDistance', options.particles?.connectDistance, 150, { min: 1 }),
|
|
543
546
|
relSpeed: pno('particles.relSpeed', options.particles?.relSpeed, 1, { min: 0 }),
|
|
544
|
-
relSize: pno('particles.relSize', options.particles?.relSize, 1, { min:
|
|
547
|
+
relSize: pno('particles.relSize', options.particles?.relSize, 1, { min: 0 }),
|
|
545
548
|
rotationSpeed: pno('particles.rotationSpeed', options.particles?.rotationSpeed, 2, { min: 0 }) / 100,
|
|
546
549
|
},
|
|
547
550
|
gravity: {
|
package/dist/index.mjs
CHANGED
|
@@ -14,12 +14,12 @@ function Mulberry32(seed) {
|
|
|
14
14
|
},
|
|
15
15
|
};
|
|
16
16
|
}
|
|
17
|
-
// Mulberry32 is ±
|
|
17
|
+
// Mulberry32 is ±392% faster than Math.random()
|
|
18
18
|
// Benchmark: https://jsbm.dev/muLCWR9RJCbmy
|
|
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.1
|
|
22
|
+
static version = "4.2.1";
|
|
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 */
|
|
@@ -235,11 +235,12 @@ class CanvasParticles {
|
|
|
235
235
|
return;
|
|
236
236
|
const len = this.particleCount;
|
|
237
237
|
const particles = this.particles;
|
|
238
|
-
const
|
|
239
|
-
const
|
|
240
|
-
const
|
|
241
|
-
const
|
|
242
|
-
const
|
|
238
|
+
const connectDist = this.option.particles.connectDist;
|
|
239
|
+
const gravRepulsiveMult = connectDist * this.option.gravity.repulsive * step;
|
|
240
|
+
const gravPullingMult = connectDist * this.option.gravity.pulling * step;
|
|
241
|
+
const maxRepulsiveDist = connectDist / 2;
|
|
242
|
+
const maxRepulsiveDistSq = maxRepulsiveDist * maxRepulsiveDist;
|
|
243
|
+
const eps = (connectDist * connectDist) / 256;
|
|
243
244
|
for (let i = 0; i < len; i++) {
|
|
244
245
|
const particleA = particles[i];
|
|
245
246
|
for (let j = i + 1; j < len; j++) {
|
|
@@ -248,17 +249,17 @@ class CanvasParticles {
|
|
|
248
249
|
const distX = particleA.posX - particleB.posX;
|
|
249
250
|
const distY = particleA.posY - particleB.posY;
|
|
250
251
|
const distSq = distX * distX + distY * distY;
|
|
252
|
+
if (distSq >= maxRepulsiveDistSq && !isPullingEnabled)
|
|
253
|
+
continue;
|
|
251
254
|
let angle;
|
|
252
255
|
let grav;
|
|
253
256
|
let gravMult;
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
angle = Math.atan2(particleB.posY - particleA.posY, particleB.posX - particleA.posX);
|
|
257
|
-
grav = Math.pow(1 / Math.sqrt(distSq), 1.8);
|
|
257
|
+
angle = Math.atan2(-distY, -distX);
|
|
258
|
+
grav = 1 / (distSq + eps);
|
|
258
259
|
const angleX = Math.cos(angle);
|
|
259
260
|
const angleY = Math.sin(angle);
|
|
260
261
|
if (distSq < maxRepulsiveDistSq) {
|
|
261
|
-
gravMult =
|
|
262
|
+
gravMult = grav * gravRepulsiveMult;
|
|
262
263
|
const gravX = angleX * gravMult;
|
|
263
264
|
const gravY = angleY * gravMult;
|
|
264
265
|
particleA.velX -= gravX;
|
|
@@ -268,7 +269,7 @@ class CanvasParticles {
|
|
|
268
269
|
}
|
|
269
270
|
if (!isPullingEnabled)
|
|
270
271
|
continue;
|
|
271
|
-
gravMult =
|
|
272
|
+
gravMult = grav * gravPullingMult;
|
|
272
273
|
const gravX = angleX * gravMult;
|
|
273
274
|
const gravY = angleY * gravMult;
|
|
274
275
|
particleA.velX += gravX;
|
|
@@ -402,9 +403,9 @@ class CanvasParticles {
|
|
|
402
403
|
const particles = this.particles;
|
|
403
404
|
const ctx = this.ctx;
|
|
404
405
|
const maxDist = this.option.particles.connectDist;
|
|
405
|
-
const maxDistSq = maxDist
|
|
406
|
+
const maxDistSq = maxDist * maxDist;
|
|
406
407
|
const halfMaxDist = maxDist / 2;
|
|
407
|
-
const halfMaxDistSq = halfMaxDist
|
|
408
|
+
const halfMaxDistSq = halfMaxDist * halfMaxDist;
|
|
408
409
|
const drawAll = maxDist >= Math.min(this.canvas.width, this.canvas.height);
|
|
409
410
|
const maxWorkPerParticle = maxDistSq * this.option.particles.maxWork;
|
|
410
411
|
const alpha = this.color.alpha;
|
|
@@ -418,7 +419,7 @@ class CanvasParticles {
|
|
|
418
419
|
// Code in this scope runs O(n^2) times per frame!
|
|
419
420
|
const particleB = particles[j];
|
|
420
421
|
// Don't draw the line if it wouldn't be visible
|
|
421
|
-
if (!
|
|
422
|
+
if (!drawAll && !this.#isLineVisible(particleA, particleB))
|
|
422
423
|
continue;
|
|
423
424
|
const distX = particleA.x - particleB.x;
|
|
424
425
|
const distY = particleA.y - particleB.y;
|
|
@@ -463,7 +464,8 @@ class CanvasParticles {
|
|
|
463
464
|
this.ctx.strokeStyle = this.color.hex;
|
|
464
465
|
this.ctx.lineWidth = 1;
|
|
465
466
|
this.#renderParticles();
|
|
466
|
-
this
|
|
467
|
+
if (this.options.particles.drawLines)
|
|
468
|
+
this.#renderConnections();
|
|
467
469
|
}
|
|
468
470
|
/** @private Main animation loop that updates and renders the particles */
|
|
469
471
|
#animation() {
|
|
@@ -526,20 +528,21 @@ class CanvasParticles {
|
|
|
526
528
|
stopOnLeave: !!(options.animation?.stopOnLeave ?? true),
|
|
527
529
|
},
|
|
528
530
|
mouse: {
|
|
529
|
-
interactionType: pno('mouse.interactionType', options.mouse?.interactionType,
|
|
531
|
+
interactionType: ~~pno('mouse.interactionType', options.mouse?.interactionType, CanvasParticles.interactionType.MOVE, { min: 0, max: 2 }),
|
|
530
532
|
connectDistMult: pno('mouse.connectDistMult', options.mouse?.connectDistMult, 2 / 3),
|
|
531
533
|
connectDist: 1 /* post processed */,
|
|
532
534
|
distRatio: pno('mouse.distRatio', options.mouse?.distRatio, 2 / 3),
|
|
533
535
|
},
|
|
534
536
|
particles: {
|
|
535
537
|
regenerateOnResize: !!options.particles?.regenerateOnResize,
|
|
538
|
+
drawLines: !!(options.particles?.drawLines ?? true),
|
|
536
539
|
color: options.particles?.color ?? 'black',
|
|
537
|
-
ppm: pno('particles.ppm', options.particles?.ppm, 100),
|
|
538
|
-
max: pno('particles.max', options.particles?.max, Infinity),
|
|
539
|
-
maxWork: pno('particles.maxWork', options.particles?.maxWork, Infinity, { min: 0 }),
|
|
540
|
-
connectDist: pno('particles.connectDistance', options.particles?.connectDistance, 150, { min: 1 }),
|
|
540
|
+
ppm: ~~pno('particles.ppm', options.particles?.ppm, 100),
|
|
541
|
+
max: Math.round(pno('particles.max', options.particles?.max, Infinity)),
|
|
542
|
+
maxWork: Math.round(pno('particles.maxWork', options.particles?.maxWork, Infinity, { min: 0 })),
|
|
543
|
+
connectDist: ~~pno('particles.connectDistance', options.particles?.connectDistance, 150, { min: 1 }),
|
|
541
544
|
relSpeed: pno('particles.relSpeed', options.particles?.relSpeed, 1, { min: 0 }),
|
|
542
|
-
relSize: pno('particles.relSize', options.particles?.relSize, 1, { min:
|
|
545
|
+
relSize: pno('particles.relSize', options.particles?.relSize, 1, { min: 0 }),
|
|
543
546
|
rotationSpeed: pno('particles.rotationSpeed', options.particles?.rotationSpeed, 2, { min: 0 }) / 100,
|
|
544
547
|
},
|
|
545
548
|
gravity: {
|
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.1.6";static MAX_DT=20;static BASE_DT=1e3/60;static interactionType=Object.freeze({NONE:0,SHIFT:1,MOVE:2});static canvasIntersectionObserver=new IntersectionObserver(t=>{for(let i=0;i<t.length;i++){const e=t[i],s=e.target,n=s.instance;if(!n.options?.animation)return;(s.inViewbox=e.isIntersecting)?n.options.animation?.startOnEnter&&n.start({auto:!0}):n.options.animation?.stopOnLeave&&n.stop({auto:!0,clear:!1})}});static canvasResizeObserver=new ResizeObserver(t=>{for(let i=0;i<t.length;i++){t[i].target.instance.updateCanvasRect()}for(let i=0;i<t.length;i++){t[i].target.instance.resizeCanvas()}});static defaultIfNaN=(t,i)=>isNaN(+t)?i:+t;static parseNumericOption=(t,i,s,n)=>{if(null==i)return s;const{min:o=-1/0,max:a=1/0}=n??{};return isFinite(o)&&i<o?console.warn(new RangeError(`option.${t} was clamped to ${o} as ${i} is too low`)):isFinite(a)&&i>a&&console.warn(new RangeError(`option.${t} was clamped to ${a} as ${i} is too high`)),e.defaultIfNaN(Math.min(Math.max(i??s,o),a),s)};canvas;ctx;enableAnimating=!1;isAnimating=!1;lastAnimationFrame=0;particles=[];particleCount=0;clientX=1/0;clientY=1/0;mouseX=1/0;mouseY=1/0;width;height;offX;offY;option;color;constructor(t,i={}){let s;if(t instanceof HTMLCanvasElement)s=t;else{if("string"!=typeof t)throw new TypeError("selector is not a string and neither a HTMLCanvasElement itself");if(s=document.querySelector(t),!(s instanceof HTMLCanvasElement))throw new Error("selector does not point to a canvas")}this.canvas=s,this.canvas.instance=this,this.canvas.inViewbox=!0;const n=this.canvas.getContext("2d");if(!n)throw new Error("failed to get 2D context from canvas");this.ctx=n,this.options=i,e.canvasIntersectionObserver.observe(this.canvas),e.canvasResizeObserver.observe(this.canvas),this.resizeCanvas=this.resizeCanvas.bind(this),this.handleMouseMove=this.handleMouseMove.bind(this),this.handleScroll=this.handleScroll.bind(this),this.updateCanvasRect(),this.resizeCanvas(),window.addEventListener("mousemove",this.handleMouseMove,{passive:!0}),window.addEventListener("scroll",this.handleScroll,{passive:!0})}updateCanvasRect(){const{top:t,left:i,width:e,height:s}=this.canvas.getBoundingClientRect();this.canvas.rect={top:t,left:i,width:e,height:s}}handleMouseMove(t){this.enableAnimating&&(this.clientX=t.clientX,this.clientY=t.clientY,this.isAnimating&&this.updateMousePos())}handleScroll(){this.enableAnimating&&(this.updateCanvasRect(),this.isAnimating&&this.updateMousePos())}updateMousePos(){const{top:t,left:i}=this.canvas.rect;this.mouseX=this.clientX-i,this.mouseY=this.clientY-t}resizeCanvas(){const t=this.canvas.width=this.canvas.rect.width,i=this.canvas.height=this.canvas.rect.height;this.mouseX=1/0,this.mouseY=1/0,this.width=Math.max(t+2*this.option.particles.connectDist,1),this.height=Math.max(i+2*this.option.particles.connectDist,1),this.offX=(t-this.width)/2,this.offY=(i-this.height)/2,this.option.particles.regenerateOnResize||0===this.particles.length?this.newParticles():this.matchParticleCount({updateBounds:!0}),this.isAnimating&&this.#t()}#i(){const t=this.option.particles.ppm*this.width*this.height/1e6|0;if(this.particleCount=Math.min(this.option.particles.max,t),!isFinite(this.particleCount))throw new RangeError("particleCount must be finite")}newParticles(){this.#i(),this.particles=[];for(let t=0;t<this.particleCount;t++)this.createParticle()}matchParticleCount({updateBounds:t=!1}={}){for(this.#i(),this.particles=this.particles.slice(0,this.particleCount),t&&this.particles.forEach(t=>this.#e(t));this.particleCount>this.particles.length;)this.createParticle()}createParticle(e,s,n,o,a){const r={posX:e="number"==typeof e?e-this.offX:i()*this.width,posY:s="number"==typeof s?s-this.offY:i()*this.height,x:e,y:s,velX:0,velY:0,offX:0,offY:0,dir:n||i()*t,speed:o||(.5+.5*i())*this.option.particles.relSpeed,size:a||(.5+i()**5*2)*this.option.particles.relSize,gridPos:{x:1,y:1},isVisible:!1};this.#e(r),this.particles.push(r)}#e(t){t.bounds={top:-t.size,right:this.canvas.width+t.size,bottom:this.canvas.height+t.size,left:-t.size}}#s(t){const i=this.option.gravity.repulsive>0,e=this.option.gravity.pulling>0;if(!i&&!e)return;const s=this.particleCount,n=this.particles,o=this.option.particles.connectDist*this.option.gravity.repulsive*t,a=this.option.particles.connectDist*this.option.gravity.pulling*t,r=(this.option.particles.connectDist/2)**2,c=.1*this.option.particles.connectDist*t;for(let t=0;t<s;t++){const i=n[t];for(let h=t+1;h<s;h++){const t=n[h],s=i.posX-t.posX,l=i.posY-t.posY,p=s*s+l*l;let u,d,f;if(p>=r&&!e)continue;u=Math.atan2(t.posY-i.posY,t.posX-i.posX),d=Math.pow(1/Math.sqrt(p),1.8);const m=Math.cos(u),v=Math.sin(u);if(p<r){f=Math.min(c,d*o);const e=m*f,s=v*f;i.velX-=e,i.velY-=s,t.velX+=e,t.velY+=s}if(!e)continue;f=Math.min(c,d*a);const g=m*f,y=v*f;i.velX+=g,i.velY+=y,t.velX-=g,t.velY-=y}}}#n(i){const s=this.particleCount,n=this.particles,o=this.width,a=this.height,r=this.offX,c=this.offY,h=this.mouseX,l=this.mouseY,p=this.option.particles.rotationSpeed*i,u=this.option.gravity.friction,d=this.option.mouse.connectDist,f=this.option.mouse.distRatio,m=this.option.mouse.interactionType===e.interactionType.NONE,v=this.option.mouse.interactionType===e.interactionType.MOVE,g=1-Math.pow(.75,i);for(let e=0;e<s;e++){const s=n[e];s.dir+=2*(Math.random()-.5)*p*i,s.dir%=t;const y=Math.sin(s.dir)*s.speed,x=Math.cos(s.dir)*s.speed;s.posX+=(y+s.velX)*i,s.posY+=(x+s.velY)*i,s.posX%=o,s.posX<0&&(s.posX+=o),s.posY%=a,s.posY<0&&(s.posY+=a),s.velX*=Math.pow(u,i),s.velY*=Math.pow(u,i);const b=s.posX+r-h,M=s.posY+c-l;if(!m){const t=d/Math.hypot(b,M);f<t?(s.offX+=(t*b-b-s.offX)*g,s.offY+=(t*M-M-s.offY)*g):(s.offX-=s.offX*g,s.offY-=s.offY*g)}s.x=s.posX+s.offX,s.y=s.posY+s.offY,v&&(s.posX=s.x,s.posY=s.y),s.x+=r,s.y+=c,this.#o(s),s.isVisible=1===s.gridPos.x&&1===s.gridPos.y}}#o(t){t.gridPos.x=+(t.x>=t.bounds.left)+ +(t.x>t.bounds.right),t.gridPos.y=+(t.y>=t.bounds.top)+ +(t.y>t.bounds.bottom)}#a(t,i){return!(!t.isVisible&&!i.isVisible)||!(t.gridPos.x===i.gridPos.x&&1!==t.gridPos.x||t.gridPos.y===i.gridPos.y&&1!==t.gridPos.y)}#r(){const i=this.particleCount,e=this.particles,s=this.ctx;for(let n=0;n<i;n++){const i=e[n];i.isVisible&&(i.size>1?(s.beginPath(),s.arc(i.x,i.y,i.size,0,t),s.fill(),s.closePath()):s.fillRect(i.x-i.size,i.y-i.size,2*i.size,2*i.size))}}#c(){const t=this.particleCount,i=this.particles,e=this.ctx,s=this.option.particles.connectDist,n=s**2,o=(s/2)**2,a=s>=Math.min(this.canvas.width,this.canvas.height),r=n*this.option.particles.maxWork,c=this.color.alpha,h=this.color.alpha*s,l=[];for(let s=0;s<t;s++){const p=i[s];let u=0;for(let d=s+1;d<t;d++){const t=i[d];if(!a&&!this.#a(p,t))continue;const s=p.x-t.x,f=p.y-t.y,m=s*s+f*f;if(!(m>n)&&(m>o?(e.globalAlpha=h/Math.sqrt(m)-c,e.beginPath(),e.moveTo(p.x,p.y),e.lineTo(t.x,t.y),e.stroke()):l.push([p.x,p.y,t.x,t.y]),(u+=m)>=r))break}}if(l.length){e.globalAlpha=c,e.beginPath();for(let t=0;t<l.length;t++){const i=l[t];e.moveTo(i[0],i[1]),e.lineTo(i[2],i[3])}e.stroke()}}#t(){this.ctx.clearRect(0,0,this.canvas.width,this.canvas.height),this.ctx.globalAlpha=this.color.alpha,this.ctx.fillStyle=this.color.hex,this.ctx.strokeStyle=this.color.hex,this.ctx.lineWidth=1,this.#r(),this.#c()}#h(){if(!this.isAnimating)return;requestAnimationFrame(()=>this.#h());const t=performance.now(),i=Math.min(t-this.lastAnimationFrame,e.MAX_DT)/e.BASE_DT;this.#s(i),this.#n(i),this.#t(),this.lastAnimationFrame=t}start({auto:t=!1}={}){return this.isAnimating||t&&!this.enableAnimating||(this.enableAnimating=!0,this.isAnimating=!0,this.updateCanvasRect(),requestAnimationFrame(()=>this.#h())),!this.canvas.inViewbox&&this.option.animation.startOnEnter&&(this.isAnimating=!1),this}stop({auto:t=!1,clear:i=!0}={}){return t||(this.enableAnimating=!1),this.isAnimating=!1,!1!==i&&this.ctx.clearRect(0,0,this.canvas.width,this.canvas.height),!0}destroy(){this.stop(),e.canvasIntersectionObserver.unobserve(this.canvas),e.canvasResizeObserver.unobserve(this.canvas),window.removeEventListener("mousemove",this.handleMouseMove),window.removeEventListener("scroll",this.handleScroll),this.canvas?.remove(),Object.keys(this).forEach(t=>delete this[t])}set options(t){const i=e.parseNumericOption;this.option={background:t.background??!1,animation:{startOnEnter:!!(t.animation?.startOnEnter??1),stopOnLeave:!!(t.animation?.stopOnLeave??1)},mouse:{interactionType:i("mouse.interactionType",t.mouse?.interactionType,1),connectDistMult:i("mouse.connectDistMult",t.mouse?.connectDistMult,2/3),connectDist:1,distRatio:i("mouse.distRatio",t.mouse?.distRatio,2/3)},particles:{regenerateOnResize:!!t.particles?.regenerateOnResize,color:t.particles?.color??"black",ppm:i("particles.ppm",t.particles?.ppm,100),max:i("particles.max",t.particles?.max,1/0),maxWork: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:1}),rotationSpeed:i("particles.rotationSpeed",t.particles?.rotationSpeed,2,{min:0})/100},gravity:{repulsive:i("gravity.repulsive",t.gravity?.repulsive,0),pulling:i("gravity.pulling",t.gravity?.pulling,0),friction:i("gravity.friction",t.gravity?.friction,.8,{min:0,max:1})}},this.setBackground(this.option.background),this.setMouseConnectDistMult(this.option.mouse.connectDistMult),this.setParticleColor(this.option.particles.color)}get options(){return this.option}setBackground(t){if(t){if("string"!=typeof t)throw new TypeError("background is not a string");this.canvas.style.background=this.option.background=t}}setMouseConnectDistMult(t){this.option.mouse.connectDist=this.option.particles.connectDist*(isNaN(t)?2/3:t)}setParticleColor(t){if(this.ctx.fillStyle=t,"#"===String(this.ctx.fillStyle)[0])this.color={hex:String(this.ctx.fillStyle),alpha:1};else{let t=String(this.ctx.fillStyle).split(",").at(-1);t=t?.slice(1,-1)??"1",this.ctx.fillStyle=String(this.ctx.fillStyle).split(",").slice(0,-1).join(",")+", 1)",this.color={hex:String(this.ctx.fillStyle),alpha:isNaN(+t)?1:+t}}}}return e});
|
|
1
|
+
!function(t,i){"object"==typeof exports&&"undefined"!=typeof module?module.exports=i():"function"==typeof define&&define.amd?define(i):(t="undefined"!=typeof globalThis?globalThis:t||self).CanvasParticles=i()}(this,function(){"use strict";const t=2*Math.PI;const i=function(t){let i=t>>>0;return{next(){let t=i+1831565813|0;return i=t,t=Math.imul(t^t>>>15,1|t),t^=t+Math.imul(t^t>>>7,61|t),((t^t>>>14)>>>0)/4294967296}}}(Math.random()*2**32).next;class e{static version="4.2.1";static MAX_DT=20;static BASE_DT=1e3/60;static interactionType=Object.freeze({NONE:0,SHIFT:1,MOVE:2});static canvasIntersectionObserver=new IntersectionObserver(t=>{for(let i=0;i<t.length;i++){const e=t[i],s=e.target,n=s.instance;if(!n.options?.animation)return;(s.inViewbox=e.isIntersecting)?n.options.animation?.startOnEnter&&n.start({auto:!0}):n.options.animation?.stopOnLeave&&n.stop({auto:!0,clear:!1})}});static canvasResizeObserver=new ResizeObserver(t=>{for(let i=0;i<t.length;i++){t[i].target.instance.updateCanvasRect()}for(let i=0;i<t.length;i++){t[i].target.instance.resizeCanvas()}});static defaultIfNaN=(t,i)=>isNaN(+t)?i:+t;static parseNumericOption=(t,i,s,n)=>{if(null==i)return s;const{min:o=-1/0,max:a=1/0}=n??{};return isFinite(o)&&i<o?console.warn(new RangeError(`option.${t} was clamped to ${o} as ${i} is too low`)):isFinite(a)&&i>a&&console.warn(new RangeError(`option.${t} was clamped to ${a} as ${i} is too high`)),e.defaultIfNaN(Math.min(Math.max(i??s,o),a),s)};canvas;ctx;enableAnimating=!1;isAnimating=!1;lastAnimationFrame=0;particles=[];particleCount=0;clientX=1/0;clientY=1/0;mouseX=1/0;mouseY=1/0;width;height;offX;offY;option;color;constructor(t,i={}){let s;if(t instanceof HTMLCanvasElement)s=t;else{if("string"!=typeof t)throw new TypeError("selector is not a string and neither a HTMLCanvasElement itself");if(s=document.querySelector(t),!(s instanceof HTMLCanvasElement))throw new Error("selector does not point to a canvas")}this.canvas=s,this.canvas.instance=this,this.canvas.inViewbox=!0;const n=this.canvas.getContext("2d");if(!n)throw new Error("failed to get 2D context from canvas");this.ctx=n,this.options=i,e.canvasIntersectionObserver.observe(this.canvas),e.canvasResizeObserver.observe(this.canvas),this.resizeCanvas=this.resizeCanvas.bind(this),this.handleMouseMove=this.handleMouseMove.bind(this),this.handleScroll=this.handleScroll.bind(this),this.updateCanvasRect(),this.resizeCanvas(),window.addEventListener("mousemove",this.handleMouseMove,{passive:!0}),window.addEventListener("scroll",this.handleScroll,{passive:!0})}updateCanvasRect(){const{top:t,left:i,width:e,height:s}=this.canvas.getBoundingClientRect();this.canvas.rect={top:t,left:i,width:e,height:s}}handleMouseMove(t){this.enableAnimating&&(this.clientX=t.clientX,this.clientY=t.clientY,this.isAnimating&&this.updateMousePos())}handleScroll(){this.enableAnimating&&(this.updateCanvasRect(),this.isAnimating&&this.updateMousePos())}updateMousePos(){const{top:t,left:i}=this.canvas.rect;this.mouseX=this.clientX-i,this.mouseY=this.clientY-t}resizeCanvas(){const t=this.canvas.width=this.canvas.rect.width,i=this.canvas.height=this.canvas.rect.height;this.mouseX=1/0,this.mouseY=1/0,this.width=Math.max(t+2*this.option.particles.connectDist,1),this.height=Math.max(i+2*this.option.particles.connectDist,1),this.offX=(t-this.width)/2,this.offY=(i-this.height)/2,this.option.particles.regenerateOnResize||0===this.particles.length?this.newParticles():this.matchParticleCount({updateBounds:!0}),this.isAnimating&&this.#t()}#i(){const t=this.option.particles.ppm*this.width*this.height/1e6|0;if(this.particleCount=Math.min(this.option.particles.max,t),!isFinite(this.particleCount))throw new RangeError("particleCount must be finite")}newParticles(){this.#i(),this.particles=[];for(let t=0;t<this.particleCount;t++)this.createParticle()}matchParticleCount({updateBounds:t=!1}={}){for(this.#i(),this.particles=this.particles.slice(0,this.particleCount),t&&this.particles.forEach(t=>this.#e(t));this.particleCount>this.particles.length;)this.createParticle()}createParticle(e,s,n,o,a){const r={posX:e="number"==typeof e?e-this.offX:i()*this.width,posY:s="number"==typeof s?s-this.offY:i()*this.height,x:e,y:s,velX:0,velY:0,offX:0,offY:0,dir:n||i()*t,speed:o||(.5+.5*i())*this.option.particles.relSpeed,size:a||(.5+i()**5*2)*this.option.particles.relSize,gridPos:{x:1,y:1},isVisible:!1};this.#e(r),this.particles.push(r)}#e(t){t.bounds={top:-t.size,right:this.canvas.width+t.size,bottom:this.canvas.height+t.size,left:-t.size}}#s(t){const i=this.option.gravity.repulsive>0,e=this.option.gravity.pulling>0;if(!i&&!e)return;const s=this.particleCount,n=this.particles,o=this.option.particles.connectDist,a=o*this.option.gravity.repulsive*t,r=o*this.option.gravity.pulling*t,c=o/2,h=c*c,l=o*o/256;for(let t=0;t<s;t++){const i=n[t];for(let o=t+1;o<s;o++){const t=n[o],s=i.posX-t.posX,c=i.posY-t.posY,p=s*s+c*c;if(p>=h&&!e)continue;let u,d,f;u=Math.atan2(-c,-s),d=1/(p+l);const m=Math.cos(u),v=Math.sin(u);if(p<h){f=d*a;const e=m*f,s=v*f;i.velX-=e,i.velY-=s,t.velX+=e,t.velY+=s}if(!e)continue;f=d*r;const g=m*f,y=v*f;i.velX+=g,i.velY+=y,t.velX-=g,t.velY-=y}}}#n(i){const s=this.particleCount,n=this.particles,o=this.width,a=this.height,r=this.offX,c=this.offY,h=this.mouseX,l=this.mouseY,p=this.option.particles.rotationSpeed*i,u=this.option.gravity.friction,d=this.option.mouse.connectDist,f=this.option.mouse.distRatio,m=this.option.mouse.interactionType===e.interactionType.NONE,v=this.option.mouse.interactionType===e.interactionType.MOVE,g=1-Math.pow(.75,i);for(let e=0;e<s;e++){const s=n[e];s.dir+=2*(Math.random()-.5)*p*i,s.dir%=t;const y=Math.sin(s.dir)*s.speed,x=Math.cos(s.dir)*s.speed;s.posX+=(y+s.velX)*i,s.posY+=(x+s.velY)*i,s.posX%=o,s.posX<0&&(s.posX+=o),s.posY%=a,s.posY<0&&(s.posY+=a),s.velX*=Math.pow(u,i),s.velY*=Math.pow(u,i);const b=s.posX+r-h,w=s.posY+c-l;if(!m){const t=d/Math.hypot(b,w);f<t?(s.offX+=(t*b-b-s.offX)*g,s.offY+=(t*w-w-s.offY)*g):(s.offX-=s.offX*g,s.offY-=s.offY*g)}s.x=s.posX+s.offX,s.y=s.posY+s.offY,v&&(s.posX=s.x,s.posY=s.y),s.x+=r,s.y+=c,this.#o(s),s.isVisible=1===s.gridPos.x&&1===s.gridPos.y}}#o(t){t.gridPos.x=+(t.x>=t.bounds.left)+ +(t.x>t.bounds.right),t.gridPos.y=+(t.y>=t.bounds.top)+ +(t.y>t.bounds.bottom)}#a(t,i){return!(!t.isVisible&&!i.isVisible)||!(t.gridPos.x===i.gridPos.x&&1!==t.gridPos.x||t.gridPos.y===i.gridPos.y&&1!==t.gridPos.y)}#r(){const i=this.particleCount,e=this.particles,s=this.ctx;for(let n=0;n<i;n++){const i=e[n];i.isVisible&&(i.size>1?(s.beginPath(),s.arc(i.x,i.y,i.size,0,t),s.fill(),s.closePath()):s.fillRect(i.x-i.size,i.y-i.size,2*i.size,2*i.size))}}#c(){const t=this.particleCount,i=this.particles,e=this.ctx,s=this.option.particles.connectDist,n=s*s,o=s/2,a=o*o,r=s>=Math.min(this.canvas.width,this.canvas.height),c=n*this.option.particles.maxWork,h=this.color.alpha,l=this.color.alpha*s,p=[];for(let s=0;s<t;s++){const o=i[s];let u=0;for(let d=s+1;d<t;d++){const t=i[d];if(!r&&!this.#a(o,t))continue;const s=o.x-t.x,f=o.y-t.y,m=s*s+f*f;if(!(m>n)&&(m>a?(e.globalAlpha=l/Math.sqrt(m)-h,e.beginPath(),e.moveTo(o.x,o.y),e.lineTo(t.x,t.y),e.stroke()):p.push([o.x,o.y,t.x,t.y]),(u+=m)>=c))break}}if(p.length){e.globalAlpha=h,e.beginPath();for(let t=0;t<p.length;t++){const i=p[t];e.moveTo(i[0],i[1]),e.lineTo(i[2],i[3])}e.stroke()}}#t(){this.ctx.clearRect(0,0,this.canvas.width,this.canvas.height),this.ctx.globalAlpha=this.color.alpha,this.ctx.fillStyle=this.color.hex,this.ctx.strokeStyle=this.color.hex,this.ctx.lineWidth=1,this.#r(),this.options.particles.drawLines&&this.#c()}#h(){if(!this.isAnimating)return;requestAnimationFrame(()=>this.#h());const t=performance.now(),i=Math.min(t-this.lastAnimationFrame,e.MAX_DT)/e.BASE_DT;this.#s(i),this.#n(i),this.#t(),this.lastAnimationFrame=t}start({auto:t=!1}={}){return this.isAnimating||t&&!this.enableAnimating||(this.enableAnimating=!0,this.isAnimating=!0,this.updateCanvasRect(),requestAnimationFrame(()=>this.#h())),!this.canvas.inViewbox&&this.option.animation.startOnEnter&&(this.isAnimating=!1),this}stop({auto:t=!1,clear:i=!0}={}){return t||(this.enableAnimating=!1),this.isAnimating=!1,!1!==i&&this.ctx.clearRect(0,0,this.canvas.width,this.canvas.height),!0}destroy(){this.stop(),e.canvasIntersectionObserver.unobserve(this.canvas),e.canvasResizeObserver.unobserve(this.canvas),window.removeEventListener("mousemove",this.handleMouseMove),window.removeEventListener("scroll",this.handleScroll),this.canvas?.remove(),Object.keys(this).forEach(t=>delete this[t])}set options(t){const i=e.parseNumericOption;this.option={background:t.background??!1,animation:{startOnEnter:!!(t.animation?.startOnEnter??1),stopOnLeave:!!(t.animation?.stopOnLeave??1)},mouse:{interactionType:~~i("mouse.interactionType",t.mouse?.interactionType,e.interactionType.MOVE,{min:0,max:2}),connectDistMult:i("mouse.connectDistMult",t.mouse?.connectDistMult,2/3),connectDist:1,distRatio:i("mouse.distRatio",t.mouse?.distRatio,2/3)},particles:{regenerateOnResize:!!t.particles?.regenerateOnResize,drawLines:!!(t.particles?.drawLines??1),color:t.particles?.color??"black",ppm:~~i("particles.ppm",t.particles?.ppm,100),max:Math.round(i("particles.max",t.particles?.max,1/0)),maxWork:Math.round(i("particles.maxWork",t.particles?.maxWork,1/0,{min:0})),connectDist:~~i("particles.connectDistance",t.particles?.connectDistance,150,{min:1}),relSpeed:i("particles.relSpeed",t.particles?.relSpeed,1,{min:0}),relSize:i("particles.relSize",t.particles?.relSize,1,{min:0}),rotationSpeed:i("particles.rotationSpeed",t.particles?.rotationSpeed,2,{min:0})/100},gravity:{repulsive:i("gravity.repulsive",t.gravity?.repulsive,0),pulling:i("gravity.pulling",t.gravity?.pulling,0),friction:i("gravity.friction",t.gravity?.friction,.8,{min:0,max:1})}},this.setBackground(this.option.background),this.setMouseConnectDistMult(this.option.mouse.connectDistMult),this.setParticleColor(this.option.particles.color)}get options(){return this.option}setBackground(t){if(t){if("string"!=typeof t)throw new TypeError("background is not a string");this.canvas.style.background=this.option.background=t}}setMouseConnectDistMult(t){this.option.mouse.connectDist=this.option.particles.connectDist*(isNaN(t)?2/3:t)}setParticleColor(t){if(this.ctx.fillStyle=t,"#"===String(this.ctx.fillStyle)[0])this.color={hex:String(this.ctx.fillStyle),alpha:1};else{let t=String(this.ctx.fillStyle).split(",").at(-1);t=t?.slice(1,-1)??"1",this.ctx.fillStyle=String(this.ctx.fillStyle).split(",").slice(0,-1).join(",")+", 1)",this.color={hex:String(this.ctx.fillStyle),alpha:isNaN(+t)?1:+t}}}}return e});
|
package/dist/types/options.d.ts
CHANGED
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// Copyright (c) 2022–2025 Kyle Hoeckman, MIT License
|
|
2
2
|
// https://github.com/Khoeckman/canvasparticles-js/blob/main/LICENSE
|
|
3
3
|
|
|
4
|
-
import type { CanvasParticlesCanvas, Particle,
|
|
4
|
+
import type { CanvasParticlesCanvas, Particle, ContextColor, LineSegment } from './types'
|
|
5
5
|
import type { CanvasParticlesOptions, CanvasParticlesOptionsInput } from './types/options'
|
|
6
6
|
|
|
7
7
|
const TWO_PI = 2 * Math.PI
|
|
@@ -21,7 +21,7 @@ function Mulberry32(seed: number) {
|
|
|
21
21
|
}
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
-
// Mulberry32 is ±
|
|
24
|
+
// Mulberry32 is ±392% faster than Math.random()
|
|
25
25
|
// Benchmark: https://jsbm.dev/muLCWR9RJCbmy
|
|
26
26
|
// Spectral test: /demo/mulberry32.html
|
|
27
27
|
const prng = Mulberry32(Math.random() * 2 ** 32).next
|
|
@@ -281,11 +281,12 @@ export default class CanvasParticles {
|
|
|
281
281
|
|
|
282
282
|
const len = this.particleCount
|
|
283
283
|
const particles = this.particles
|
|
284
|
-
const
|
|
285
|
-
const
|
|
286
|
-
const
|
|
287
|
-
const
|
|
288
|
-
const
|
|
284
|
+
const connectDist = this.option.particles.connectDist
|
|
285
|
+
const gravRepulsiveMult = connectDist * this.option.gravity.repulsive * step
|
|
286
|
+
const gravPullingMult = connectDist * this.option.gravity.pulling * step
|
|
287
|
+
const maxRepulsiveDist = connectDist / 2
|
|
288
|
+
const maxRepulsiveDistSq = maxRepulsiveDist * maxRepulsiveDist
|
|
289
|
+
const eps = (connectDist * connectDist) / 256
|
|
289
290
|
|
|
290
291
|
for (let i = 0; i < len; i++) {
|
|
291
292
|
const particleA = particles[i]
|
|
@@ -298,19 +299,19 @@ export default class CanvasParticles {
|
|
|
298
299
|
const distY = particleA.posY - particleB.posY
|
|
299
300
|
const distSq = distX * distX + distY * distY
|
|
300
301
|
|
|
302
|
+
if (distSq >= maxRepulsiveDistSq && !isPullingEnabled) continue
|
|
303
|
+
|
|
301
304
|
let angle
|
|
302
305
|
let grav
|
|
303
306
|
let gravMult
|
|
304
307
|
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
angle = Math.atan2(particleB.posY - particleA.posY, particleB.posX - particleA.posX)
|
|
308
|
-
grav = Math.pow(1 / Math.sqrt(distSq), 1.8)
|
|
308
|
+
angle = Math.atan2(-distY, -distX)
|
|
309
|
+
grav = 1 / (distSq + eps)
|
|
309
310
|
const angleX = Math.cos(angle)
|
|
310
311
|
const angleY = Math.sin(angle)
|
|
311
312
|
|
|
312
313
|
if (distSq < maxRepulsiveDistSq) {
|
|
313
|
-
gravMult =
|
|
314
|
+
gravMult = grav * gravRepulsiveMult
|
|
314
315
|
const gravX = angleX * gravMult
|
|
315
316
|
const gravY = angleY * gravMult
|
|
316
317
|
particleA.velX -= gravX
|
|
@@ -321,7 +322,7 @@ export default class CanvasParticles {
|
|
|
321
322
|
|
|
322
323
|
if (!isPullingEnabled) continue
|
|
323
324
|
|
|
324
|
-
gravMult =
|
|
325
|
+
gravMult = grav * gravPullingMult
|
|
325
326
|
const gravX = angleX * gravMult
|
|
326
327
|
const gravY = angleY * gravMult
|
|
327
328
|
particleA.velX += gravX
|
|
@@ -473,9 +474,9 @@ export default class CanvasParticles {
|
|
|
473
474
|
const particles = this.particles
|
|
474
475
|
const ctx = this.ctx
|
|
475
476
|
const maxDist = this.option.particles.connectDist
|
|
476
|
-
const maxDistSq = maxDist
|
|
477
|
+
const maxDistSq = maxDist * maxDist
|
|
477
478
|
const halfMaxDist = maxDist / 2
|
|
478
|
-
const halfMaxDistSq = halfMaxDist
|
|
479
|
+
const halfMaxDistSq = halfMaxDist * halfMaxDist
|
|
479
480
|
const drawAll = maxDist >= Math.min(this.canvas.width, this.canvas.height)
|
|
480
481
|
const maxWorkPerParticle = maxDistSq * this.option.particles.maxWork
|
|
481
482
|
const alpha = this.color.alpha
|
|
@@ -493,7 +494,7 @@ export default class CanvasParticles {
|
|
|
493
494
|
const particleB = particles[j]
|
|
494
495
|
|
|
495
496
|
// Don't draw the line if it wouldn't be visible
|
|
496
|
-
if (!
|
|
497
|
+
if (!drawAll && !this.#isLineVisible(particleA, particleB)) continue
|
|
497
498
|
|
|
498
499
|
const distX = particleA.x - particleB.x
|
|
499
500
|
const distY = particleA.y - particleB.y
|
|
@@ -545,7 +546,7 @@ export default class CanvasParticles {
|
|
|
545
546
|
this.ctx.lineWidth = 1
|
|
546
547
|
|
|
547
548
|
this.#renderParticles()
|
|
548
|
-
this.#renderConnections()
|
|
549
|
+
if (this.options.particles.drawLines) this.#renderConnections()
|
|
549
550
|
}
|
|
550
551
|
|
|
551
552
|
/** @private Main animation loop that updates and renders the particles */
|
|
@@ -621,20 +622,26 @@ export default class CanvasParticles {
|
|
|
621
622
|
stopOnLeave: !!(options.animation?.stopOnLeave ?? true),
|
|
622
623
|
},
|
|
623
624
|
mouse: {
|
|
624
|
-
interactionType: pno(
|
|
625
|
+
interactionType: ~~pno(
|
|
626
|
+
'mouse.interactionType',
|
|
627
|
+
options.mouse?.interactionType,
|
|
628
|
+
CanvasParticles.interactionType.MOVE,
|
|
629
|
+
{ min: 0, max: 2 }
|
|
630
|
+
),
|
|
625
631
|
connectDistMult: pno('mouse.connectDistMult', options.mouse?.connectDistMult, 2 / 3),
|
|
626
632
|
connectDist: 1 /* post processed */,
|
|
627
633
|
distRatio: pno('mouse.distRatio', options.mouse?.distRatio, 2 / 3),
|
|
628
634
|
},
|
|
629
635
|
particles: {
|
|
630
636
|
regenerateOnResize: !!options.particles?.regenerateOnResize,
|
|
637
|
+
drawLines: !!(options.particles?.drawLines ?? true),
|
|
631
638
|
color: options.particles?.color ?? 'black',
|
|
632
|
-
ppm: pno('particles.ppm', options.particles?.ppm, 100),
|
|
633
|
-
max: pno('particles.max', options.particles?.max, Infinity),
|
|
634
|
-
maxWork: pno('particles.maxWork', options.particles?.maxWork, Infinity, { min: 0 }),
|
|
635
|
-
connectDist: pno('particles.connectDistance', options.particles?.connectDistance, 150, { min: 1 }),
|
|
639
|
+
ppm: ~~pno('particles.ppm', options.particles?.ppm, 100),
|
|
640
|
+
max: Math.round(pno('particles.max', options.particles?.max, Infinity)),
|
|
641
|
+
maxWork: Math.round(pno('particles.maxWork', options.particles?.maxWork, Infinity, { min: 0 })),
|
|
642
|
+
connectDist: ~~pno('particles.connectDistance', options.particles?.connectDistance, 150, { min: 1 }),
|
|
636
643
|
relSpeed: pno('particles.relSpeed', options.particles?.relSpeed, 1, { min: 0 }),
|
|
637
|
-
relSize: pno('particles.relSize', options.particles?.relSize, 1, { min:
|
|
644
|
+
relSize: pno('particles.relSize', options.particles?.relSize, 1, { min: 0 }),
|
|
638
645
|
rotationSpeed: pno('particles.rotationSpeed', options.particles?.rotationSpeed, 2, { min: 0 }) / 100,
|
|
639
646
|
},
|
|
640
647
|
gravity: {
|