@tsparticles/engine 3.0.3 → 3.1.0

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.
Files changed (124) hide show
  1. package/README.md +265 -145
  2. package/browser/Core/Canvas.js +19 -19
  3. package/browser/Core/Container.js +45 -34
  4. package/browser/Core/Engine.js +36 -20
  5. package/browser/Core/Particle.js +35 -36
  6. package/browser/Core/Particles.js +30 -24
  7. package/browser/Core/Retina.js +5 -4
  8. package/browser/Core/Utils/Circle.js +4 -3
  9. package/browser/Core/Utils/Constants.js +3 -0
  10. package/browser/Core/Utils/EventListeners.js +18 -15
  11. package/browser/Core/Utils/ExternalInteractorBase.js +1 -1
  12. package/browser/Core/Utils/InteractionManager.js +14 -6
  13. package/browser/Core/Utils/ParticlesInteractorBase.js +1 -1
  14. package/browser/Core/Utils/QuadTree.js +5 -3
  15. package/browser/Core/Utils/Vector.js +7 -2
  16. package/browser/Core/Utils/Vector3d.js +14 -9
  17. package/browser/Options/Classes/ManualParticle.js +3 -2
  18. package/browser/Options/Classes/Options.js +3 -0
  19. package/browser/Utils/CanvasUtils.js +36 -26
  20. package/browser/Utils/ColorUtils.js +124 -45
  21. package/browser/Utils/EventDispatcher.js +6 -5
  22. package/browser/Utils/HslColorManager.js +5 -5
  23. package/browser/Utils/NumberUtils.js +35 -23
  24. package/browser/Utils/RgbColorManager.js +5 -5
  25. package/browser/Utils/Utils.js +102 -19
  26. package/cjs/Core/Canvas.js +19 -19
  27. package/cjs/Core/Container.js +45 -34
  28. package/cjs/Core/Engine.js +36 -20
  29. package/cjs/Core/Particle.js +34 -35
  30. package/cjs/Core/Particles.js +30 -24
  31. package/cjs/Core/Retina.js +5 -4
  32. package/cjs/Core/Utils/Circle.js +4 -3
  33. package/cjs/Core/Utils/Constants.js +4 -1
  34. package/cjs/Core/Utils/EventListeners.js +17 -14
  35. package/cjs/Core/Utils/ExternalInteractorBase.js +1 -1
  36. package/cjs/Core/Utils/InteractionManager.js +14 -6
  37. package/cjs/Core/Utils/ParticlesInteractorBase.js +1 -1
  38. package/cjs/Core/Utils/QuadTree.js +5 -3
  39. package/cjs/Core/Utils/Vector.js +7 -2
  40. package/cjs/Core/Utils/Vector3d.js +14 -9
  41. package/cjs/Options/Classes/ManualParticle.js +3 -2
  42. package/cjs/Options/Classes/Options.js +3 -0
  43. package/cjs/Utils/CanvasUtils.js +36 -26
  44. package/cjs/Utils/ColorUtils.js +126 -45
  45. package/cjs/Utils/EventDispatcher.js +6 -5
  46. package/cjs/Utils/HslColorManager.js +5 -5
  47. package/cjs/Utils/NumberUtils.js +37 -24
  48. package/cjs/Utils/RgbColorManager.js +5 -5
  49. package/cjs/Utils/Utils.js +103 -19
  50. package/esm/Core/Canvas.js +19 -19
  51. package/esm/Core/Container.js +45 -34
  52. package/esm/Core/Engine.js +36 -20
  53. package/esm/Core/Particle.js +35 -36
  54. package/esm/Core/Particles.js +30 -24
  55. package/esm/Core/Retina.js +5 -4
  56. package/esm/Core/Utils/Circle.js +4 -3
  57. package/esm/Core/Utils/Constants.js +3 -0
  58. package/esm/Core/Utils/EventListeners.js +18 -15
  59. package/esm/Core/Utils/ExternalInteractorBase.js +1 -1
  60. package/esm/Core/Utils/InteractionManager.js +14 -6
  61. package/esm/Core/Utils/ParticlesInteractorBase.js +1 -1
  62. package/esm/Core/Utils/QuadTree.js +5 -3
  63. package/esm/Core/Utils/Vector.js +7 -2
  64. package/esm/Core/Utils/Vector3d.js +14 -9
  65. package/esm/Options/Classes/ManualParticle.js +3 -2
  66. package/esm/Options/Classes/Options.js +3 -0
  67. package/esm/Utils/CanvasUtils.js +36 -26
  68. package/esm/Utils/ColorUtils.js +124 -45
  69. package/esm/Utils/EventDispatcher.js +6 -5
  70. package/esm/Utils/HslColorManager.js +5 -5
  71. package/esm/Utils/NumberUtils.js +35 -23
  72. package/esm/Utils/RgbColorManager.js +5 -5
  73. package/esm/Utils/Utils.js +102 -19
  74. package/package.json +1 -1
  75. package/report.html +2 -2
  76. package/tsparticles.engine.js +693 -334
  77. package/tsparticles.engine.min.js +1 -1
  78. package/tsparticles.engine.min.js.LICENSE.txt +1 -1
  79. package/types/Core/Interfaces/IParticleHslAnimation.d.ts +4 -4
  80. package/types/Core/Interfaces/IParticleValueAnimation.d.ts +4 -0
  81. package/types/Core/Interfaces/IShapeDrawData.d.ts +2 -2
  82. package/types/Core/Utils/Constants.d.ts +3 -0
  83. package/types/Core/Utils/ExternalInteractorBase.d.ts +1 -1
  84. package/types/Core/Utils/InteractionManager.d.ts +1 -1
  85. package/types/Core/Utils/ParticlesInteractorBase.d.ts +1 -1
  86. package/types/Core/Utils/Point.d.ts +1 -1
  87. package/types/Options/Classes/Options.d.ts +1 -0
  88. package/types/Options/Classes/Particles/Move/Move.d.ts +1 -2
  89. package/types/Options/Classes/Particles/Move/OutModes.d.ts +1 -2
  90. package/types/Options/Interfaces/IOptions.d.ts +1 -0
  91. package/types/Options/Interfaces/Interactivity/Modes/IModes.d.ts +1 -3
  92. package/types/Types/CustomEventArgs.d.ts +2 -2
  93. package/types/Types/ExportResult.d.ts +2 -2
  94. package/types/Types/ParticlesGroups.d.ts +1 -3
  95. package/types/Types/PathOptions.d.ts +1 -3
  96. package/types/Types/ShapeData.d.ts +1 -3
  97. package/types/Utils/CanvasUtils.d.ts +3 -2
  98. package/types/Utils/ColorUtils.d.ts +5 -0
  99. package/types/Utils/NumberUtils.d.ts +2 -2
  100. package/types/Utils/Utils.d.ts +9 -6
  101. package/umd/Core/Canvas.js +19 -19
  102. package/umd/Core/Container.js +46 -35
  103. package/umd/Core/Engine.js +36 -20
  104. package/umd/Core/Particle.js +35 -36
  105. package/umd/Core/Particles.js +30 -24
  106. package/umd/Core/Retina.js +5 -4
  107. package/umd/Core/Utils/Circle.js +4 -3
  108. package/umd/Core/Utils/Constants.js +4 -1
  109. package/umd/Core/Utils/EventListeners.js +17 -14
  110. package/umd/Core/Utils/ExternalInteractorBase.js +1 -1
  111. package/umd/Core/Utils/InteractionManager.js +14 -6
  112. package/umd/Core/Utils/ParticlesInteractorBase.js +1 -1
  113. package/umd/Core/Utils/QuadTree.js +5 -3
  114. package/umd/Core/Utils/Vector.js +7 -2
  115. package/umd/Core/Utils/Vector3d.js +14 -9
  116. package/umd/Options/Classes/ManualParticle.js +3 -2
  117. package/umd/Options/Classes/Options.js +3 -0
  118. package/umd/Utils/CanvasUtils.js +36 -26
  119. package/umd/Utils/ColorUtils.js +127 -46
  120. package/umd/Utils/EventDispatcher.js +6 -5
  121. package/umd/Utils/HslColorManager.js +5 -5
  122. package/umd/Utils/NumberUtils.js +38 -25
  123. package/umd/Utils/RgbColorManager.js +5 -5
  124. package/umd/Utils/Utils.js +104 -20
@@ -1,19 +1,20 @@
1
+ import { errorPrefix, millisecondsToSeconds } from "./Utils/Constants.js";
1
2
  import { getLogger, safeIntersectionObserver } from "../Utils/Utils.js";
2
3
  import { Canvas } from "./Canvas.js";
3
4
  import { EventListeners } from "./Utils/EventListeners.js";
4
5
  import { Options } from "../Options/Classes/Options.js";
5
6
  import { Particles } from "./Particles.js";
6
7
  import { Retina } from "./Retina.js";
7
- import { errorPrefix } from "./Utils/Constants.js";
8
8
  import { getRangeValue } from "../Utils/NumberUtils.js";
9
9
  import { loadOptions } from "../Utils/OptionsUtils.js";
10
10
  function guardCheck(container) {
11
11
  return container && !container.destroyed;
12
12
  }
13
- function initDelta(value, fpsLimit = 60, smooth = false) {
13
+ const defaultFps = 60;
14
+ function initDelta(value, fpsLimit = defaultFps, smooth = false) {
14
15
  return {
15
16
  value,
16
- factor: smooth ? 60 / fpsLimit : (60 * value) / 1000,
17
+ factor: smooth ? defaultFps / fpsLimit : (defaultFps * value) / millisecondsToSeconds,
17
18
  };
18
19
  }
19
20
  function loadContainerOptions(engine, container, ...sourceOptionsArr) {
@@ -31,14 +32,19 @@ export class Container {
31
32
  if (entry.target !== this.interactivity.element) {
32
33
  continue;
33
34
  }
34
- (entry.isIntersecting ? this.play : this.pause)();
35
+ if (entry.isIntersecting) {
36
+ this.play();
37
+ }
38
+ else {
39
+ this.pause();
40
+ }
35
41
  }
36
42
  };
37
43
  this._nextFrame = async (timestamp) => {
38
44
  try {
39
45
  if (!this._smooth &&
40
46
  this._lastFrameTime !== undefined &&
41
- timestamp < this._lastFrameTime + 1000 / this.fpsLimit) {
47
+ timestamp < this._lastFrameTime + millisecondsToSeconds / this.fpsLimit) {
42
48
  this.draw(false);
43
49
  return;
44
50
  }
@@ -46,7 +52,7 @@ export class Container {
46
52
  const delta = initDelta(timestamp - this._lastFrameTime, this.fpsLimit, this._smooth);
47
53
  this.addLifeTime(delta.value);
48
54
  this._lastFrameTime = timestamp;
49
- if (delta.value > 1000) {
55
+ if (delta.value > millisecondsToSeconds) {
50
56
  this.draw(false);
51
57
  return;
52
58
  }
@@ -129,8 +135,8 @@ export class Container {
129
135
  const mouseEvent = e, pos = {
130
136
  x: mouseEvent.offsetX || mouseEvent.clientX,
131
137
  y: mouseEvent.offsetY || mouseEvent.clientY,
132
- };
133
- clickOrTouchHandler(e, pos, 1);
138
+ }, radius = 1;
139
+ clickOrTouchHandler(e, pos, radius);
134
140
  };
135
141
  const touchStartHandler = () => {
136
142
  if (!guardCheck(this)) {
@@ -151,16 +157,17 @@ export class Container {
151
157
  }
152
158
  if (touched && !touchMoved) {
153
159
  const touchEvent = e;
154
- let lastTouch = touchEvent.touches[touchEvent.touches.length - 1];
160
+ const lengthOffset = 1;
161
+ let lastTouch = touchEvent.touches[touchEvent.touches.length - lengthOffset];
155
162
  if (!lastTouch) {
156
- lastTouch = touchEvent.changedTouches[touchEvent.changedTouches.length - 1];
163
+ lastTouch = touchEvent.changedTouches[touchEvent.changedTouches.length - lengthOffset];
157
164
  if (!lastTouch) {
158
165
  return;
159
166
  }
160
167
  }
161
- const element = this.canvas.element, canvasRect = element ? element.getBoundingClientRect() : undefined, pos = {
162
- x: lastTouch.clientX - (canvasRect ? canvasRect.left : 0),
163
- y: lastTouch.clientY - (canvasRect ? canvasRect.top : 0),
168
+ const element = this.canvas.element, canvasRect = element ? element.getBoundingClientRect() : undefined, minCoordinate = 0, pos = {
169
+ x: lastTouch.clientX - (canvasRect ? canvasRect.left : minCoordinate),
170
+ y: lastTouch.clientY - (canvasRect ? canvasRect.top : minCoordinate),
164
171
  };
165
172
  clickOrTouchHandler(e, pos, Math.max(lastTouch.radiusX, lastTouch.radiusY));
166
173
  }
@@ -202,10 +209,10 @@ export class Container {
202
209
  this.particles.destroy();
203
210
  this.canvas.destroy();
204
211
  for (const [, effectDrawer] of this.effectDrawers) {
205
- effectDrawer.destroy && effectDrawer.destroy(this);
212
+ effectDrawer.destroy?.(this);
206
213
  }
207
214
  for (const [, shapeDrawer] of this.shapeDrawers) {
208
- shapeDrawer.destroy && shapeDrawer.destroy(this);
215
+ shapeDrawer.destroy?.(this);
209
216
  }
210
217
  for (const key of this.effectDrawers.keys()) {
211
218
  this.effectDrawers.delete(key);
@@ -215,9 +222,10 @@ export class Container {
215
222
  }
216
223
  this._engine.clearPlugins(this);
217
224
  this.destroyed = true;
218
- const mainArr = this._engine.dom(), idx = mainArr.findIndex((t) => t === this);
219
- if (idx >= 0) {
220
- mainArr.splice(idx, 1);
225
+ const mainArr = this._engine.dom(), idx = mainArr.findIndex((t) => t === this), minIndex = 0;
226
+ if (idx >= minIndex) {
227
+ const deleteCount = 1;
228
+ mainArr.splice(idx, deleteCount);
221
229
  }
222
230
  this._engine.dispatchEvent("containerDestroyed", { container: this });
223
231
  }
@@ -226,13 +234,14 @@ export class Container {
226
234
  return;
227
235
  }
228
236
  let refreshTime = force;
229
- this._drawAnimationFrame = requestAnimationFrame(async (timestamp) => {
237
+ const frame = async (timestamp) => {
230
238
  if (refreshTime) {
231
239
  this._lastFrameTime = undefined;
232
240
  refreshTime = false;
233
241
  }
234
242
  await this._nextFrame(timestamp);
235
- });
243
+ };
244
+ this._drawAnimationFrame = requestAnimationFrame((timestamp) => void frame(timestamp));
236
245
  }
237
246
  async export(type, options = {}) {
238
247
  for (const [, plugin] of this.plugins) {
@@ -256,7 +265,7 @@ export class Container {
256
265
  }
257
266
  this.particles.handleClickMode(mode);
258
267
  for (const [, plugin] of this.plugins) {
259
- plugin.handleClickMode && plugin.handleClickMode(mode);
268
+ plugin.handleClickMode?.(mode);
260
269
  }
261
270
  }
262
271
  async init() {
@@ -289,25 +298,26 @@ export class Container {
289
298
  this.canvas.initBackground();
290
299
  this.canvas.resize();
291
300
  this.zLayers = this.actualOptions.zLayers;
292
- this._duration = getRangeValue(this.actualOptions.duration) * 1000;
293
- this._delay = getRangeValue(this.actualOptions.delay) * 1000;
301
+ this._duration = getRangeValue(this.actualOptions.duration) * millisecondsToSeconds;
302
+ this._delay = getRangeValue(this.actualOptions.delay) * millisecondsToSeconds;
294
303
  this._lifeTime = 0;
295
- this.fpsLimit = this.actualOptions.fpsLimit > 0 ? this.actualOptions.fpsLimit : 120;
304
+ const defaultFpsLimit = 120, minFpsLimit = 0;
305
+ this.fpsLimit = this.actualOptions.fpsLimit > minFpsLimit ? this.actualOptions.fpsLimit : defaultFpsLimit;
296
306
  this._smooth = this.actualOptions.smooth;
297
307
  for (const [, drawer] of this.effectDrawers) {
298
- drawer.init && (await drawer.init(this));
308
+ await drawer.init?.(this);
299
309
  }
300
310
  for (const [, drawer] of this.shapeDrawers) {
301
- drawer.init && (await drawer.init(this));
311
+ await drawer.init?.(this);
302
312
  }
303
313
  for (const [, plugin] of this.plugins) {
304
- plugin.init && (await plugin.init());
314
+ await plugin.init?.();
305
315
  }
306
316
  this._engine.dispatchEvent("containerInit", { container: this });
307
317
  this.particles.init();
308
318
  this.particles.setDensity();
309
319
  for (const [, plugin] of this.plugins) {
310
- plugin.particlesSetup && plugin.particlesSetup();
320
+ plugin.particlesSetup?.();
311
321
  }
312
322
  this._engine.dispatchEvent("particlesSetup", { container: this });
313
323
  }
@@ -330,7 +340,7 @@ export class Container {
330
340
  return;
331
341
  }
332
342
  for (const [, plugin] of this.plugins) {
333
- plugin.pause && plugin.pause();
343
+ plugin.pause?.();
334
344
  }
335
345
  if (!this.pageHidden) {
336
346
  this._paused = true;
@@ -357,7 +367,7 @@ export class Container {
357
367
  }
358
368
  }
359
369
  this._engine.dispatchEvent("containerPlay", { container: this });
360
- this.draw(needsUpdate || false);
370
+ this.draw(needsUpdate ?? false);
361
371
  }
362
372
  async refresh() {
363
373
  if (!guardCheck(this)) {
@@ -382,18 +392,19 @@ export class Container {
382
392
  await this.init();
383
393
  this.started = true;
384
394
  await new Promise((resolve) => {
385
- this._delayTimeout = setTimeout(async () => {
395
+ const start = async () => {
386
396
  this._eventListeners.addListeners();
387
397
  if (this.interactivity.element instanceof HTMLElement && this._intersectionObserver) {
388
398
  this._intersectionObserver.observe(this.interactivity.element);
389
399
  }
390
400
  for (const [, plugin] of this.plugins) {
391
- plugin.start && (await plugin.start());
401
+ await plugin.start?.();
392
402
  }
393
403
  this._engine.dispatchEvent("containerStarted", { container: this });
394
404
  this.play();
395
405
  resolve();
396
- }, this._delay);
406
+ };
407
+ this._delayTimeout = setTimeout(() => void start(), this._delay);
397
408
  });
398
409
  }
399
410
  stop() {
@@ -414,7 +425,7 @@ export class Container {
414
425
  this._intersectionObserver.unobserve(this.interactivity.element);
415
426
  }
416
427
  for (const [, plugin] of this.plugins) {
417
- plugin.stop && plugin.stop();
428
+ plugin.stop?.();
418
429
  }
419
430
  for (const key of this.plugins.keys()) {
420
431
  this.plugins.delete(key);
@@ -51,16 +51,18 @@ export class Engine {
51
51
  return res;
52
52
  }
53
53
  get version() {
54
- return "3.0.3";
54
+ return "3.1.0";
55
55
  }
56
56
  addConfig(config) {
57
- const name = config.name ?? "default";
58
- this._configs.set(name, config);
59
- this._eventDispatcher.dispatchEvent("configAdded", { data: { name, config } });
57
+ const key = config.key ?? config.name ?? "default";
58
+ this._configs.set(key, config);
59
+ this._eventDispatcher.dispatchEvent("configAdded", { data: { name: key, config } });
60
60
  }
61
61
  async addEffect(effect, drawer, refresh = true) {
62
62
  executeOnSingleOrMultiple(effect, (type) => {
63
- !this.getEffectDrawer(type) && this.effectDrawers.set(type, drawer);
63
+ if (!this.getEffectDrawer(type)) {
64
+ this.effectDrawers.set(type, drawer);
65
+ }
64
66
  });
65
67
  await this.refresh(refresh);
66
68
  }
@@ -80,20 +82,28 @@ export class Engine {
80
82
  await this.refresh(refresh);
81
83
  }
82
84
  async addPathGenerator(name, generator, refresh = true) {
83
- !this.getPathGenerator(name) && this.pathGenerators.set(name, generator);
85
+ if (!this.getPathGenerator(name)) {
86
+ this.pathGenerators.set(name, generator);
87
+ }
84
88
  await this.refresh(refresh);
85
89
  }
86
90
  async addPlugin(plugin, refresh = true) {
87
- !this.getPlugin(plugin.id) && this.plugins.push(plugin);
91
+ if (!this.getPlugin(plugin.id)) {
92
+ this.plugins.push(plugin);
93
+ }
88
94
  await this.refresh(refresh);
89
95
  }
90
96
  async addPreset(preset, options, override = false, refresh = true) {
91
- (override || !this.getPreset(preset)) && this.presets.set(preset, options);
97
+ if (override || !this.getPreset(preset)) {
98
+ this.presets.set(preset, options);
99
+ }
92
100
  await this.refresh(refresh);
93
101
  }
94
102
  async addShape(shape, drawer, refresh = true) {
95
103
  executeOnSingleOrMultiple(shape, (type) => {
96
- !this.getShapeDrawer(type) && this.shapeDrawers.set(type, drawer);
104
+ if (!this.getShapeDrawer(type)) {
105
+ this.shapeDrawers.set(type, drawer);
106
+ }
97
107
  });
98
108
  await this.refresh(refresh);
99
109
  }
@@ -111,7 +121,8 @@ export class Engine {
111
121
  domItem(index) {
112
122
  const dom = this.dom(), item = dom[index];
113
123
  if (!item || item.destroyed) {
114
- dom.splice(index, 1);
124
+ const deleteCount = 1;
125
+ dom.splice(index, deleteCount);
115
126
  return;
116
127
  }
117
128
  return item;
@@ -119,7 +130,9 @@ export class Engine {
119
130
  getAvailablePlugins(container) {
120
131
  const res = new Map();
121
132
  for (const plugin of this.plugins) {
122
- plugin.needsPlugin(container.actualOptions) && res.set(plugin.id, plugin.getPlugin(container));
133
+ if (plugin.needsPlugin(container.actualOptions)) {
134
+ res.set(plugin.id, plugin.getPlugin(container));
135
+ }
123
136
  }
124
137
  return res;
125
138
  }
@@ -160,19 +173,20 @@ export class Engine {
160
173
  this._initialized = true;
161
174
  }
162
175
  async load(params) {
163
- const id = params.id ?? params.element?.id ?? `tsparticles${Math.floor(getRandom() * 10000)}`, { index, url } = params, options = url ? await getDataFromUrl({ fallback: params.options, url, index }) : params.options;
176
+ const randomFactor = 10000, id = params.id ?? params.element?.id ?? `tsparticles${Math.floor(getRandom() * randomFactor)}`, { index, url } = params, options = url ? await getDataFromUrl({ fallback: params.options, url, index }) : params.options;
164
177
  let domContainer = params.element ?? document.getElementById(id);
165
178
  if (!domContainer) {
166
179
  domContainer = document.createElement("div");
167
180
  domContainer.id = id;
168
181
  document.body.append(domContainer);
169
182
  }
170
- const currentOptions = itemFromSingleOrMultiple(options, index), dom = this.dom(), oldIndex = dom.findIndex((v) => v.id.description === id);
171
- if (oldIndex >= 0) {
183
+ const currentOptions = itemFromSingleOrMultiple(options, index), dom = this.dom(), oldIndex = dom.findIndex((v) => v.id.description === id), minIndex = 0;
184
+ if (oldIndex >= minIndex) {
172
185
  const old = this.domItem(oldIndex);
173
186
  if (old && !old.destroyed) {
174
187
  old.destroy();
175
- dom.splice(oldIndex, 1);
188
+ const deleteCount = 1;
189
+ dom.splice(oldIndex, deleteCount);
176
190
  }
177
191
  }
178
192
  let canvasEl;
@@ -183,7 +197,8 @@ export class Engine {
183
197
  else {
184
198
  const existingCanvases = domContainer.getElementsByTagName("canvas");
185
199
  if (existingCanvases.length) {
186
- canvasEl = existingCanvases[0];
200
+ const firstIndex = 0;
201
+ canvasEl = existingCanvases[firstIndex];
187
202
  canvasEl.dataset[generatedAttribute] = "false";
188
203
  }
189
204
  else {
@@ -199,8 +214,9 @@ export class Engine {
199
214
  canvasEl.style.height = "100%";
200
215
  }
201
216
  const newItem = new Container(this, id, currentOptions);
202
- if (oldIndex >= 0) {
203
- dom.splice(oldIndex, 0, newItem);
217
+ if (oldIndex >= minIndex) {
218
+ const deleteCount = 0;
219
+ dom.splice(oldIndex, deleteCount, newItem);
204
220
  }
205
221
  else {
206
222
  dom.push(newItem);
@@ -220,14 +236,14 @@ export class Engine {
220
236
  return;
221
237
  }
222
238
  for (const updater of updaters) {
223
- updater.loadOptions && updater.loadOptions(options, ...sourceOptions);
239
+ updater.loadOptions?.(options, ...sourceOptions);
224
240
  }
225
241
  }
226
242
  async refresh(refresh = true) {
227
243
  if (!refresh) {
228
244
  return;
229
245
  }
230
- this.dom().forEach((t) => t.refresh());
246
+ await Promise.allSettled(this.dom().map((t) => t.refresh()));
231
247
  }
232
248
  removeEventListener(type, listener) {
233
249
  this._eventDispatcher.removeEventListener(type, listener);
@@ -1,12 +1,13 @@
1
- import { calcExactPositionOrRandomFromSize, clamp, getDistance, getParticleBaseVelocity, getParticleDirectionAngle, getRandom, getRangeValue, randomInRange, setRangeValue, } from "../Utils/NumberUtils.js";
1
+ import { calcExactPositionOrRandomFromSize, clamp, degToRad, getDistance, getParticleBaseVelocity, getParticleDirectionAngle, getRandom, getRangeValue, randomInRange, setRangeValue, } from "../Utils/NumberUtils.js";
2
2
  import { deepExtend, getPosition, initParticleNumericAnimationValue, isInArray, itemFromSingleOrMultiple, } from "../Utils/Utils.js";
3
+ import { errorPrefix, millisecondsToSeconds } from "./Utils/Constants.js";
3
4
  import { getHslFromAnimation, rangeColorToRgb } from "../Utils/ColorUtils.js";
4
5
  import { Interactivity } from "../Options/Classes/Interactivity/Interactivity.js";
5
6
  import { Vector } from "./Utils/Vector.js";
6
7
  import { Vector3d } from "./Utils/Vector3d.js";
7
8
  import { alterHsl } from "../Utils/CanvasUtils.js";
8
- import { errorPrefix } from "./Utils/Constants.js";
9
9
  import { loadParticlesOptions } from "../Utils/OptionsUtils.js";
10
+ const defaultRetryCount = 0, double = 2, half = 0.5, squareExp = 2;
10
11
  function loadEffectData(effect, effectOptions, id, reduceDuplicates) {
11
12
  const effectData = effectOptions.options[effect];
12
13
  if (!effectData) {
@@ -31,7 +32,7 @@ function fixOutMode(data) {
31
32
  if (!isInArray(data.outMode, data.checkModes)) {
32
33
  return;
33
34
  }
34
- const diameter = data.radius * 2;
35
+ const diameter = data.radius * double;
35
36
  if (data.coord > data.maxCoord - diameter) {
36
37
  data.setCb(-data.radius);
37
38
  }
@@ -42,7 +43,7 @@ function fixOutMode(data) {
42
43
  export class Particle {
43
44
  constructor(engine, id, container, position, overrideOptions, group) {
44
45
  this.container = container;
45
- this._calcPosition = (container, position, zIndex, tryCount = 0) => {
46
+ this._calcPosition = (container, position, zIndex, tryCount = defaultRetryCount) => {
46
47
  for (const [, plugin] of container.plugins) {
47
48
  const pluginPos = plugin.particlePosition !== undefined ? plugin.particlePosition(position, this) : undefined;
48
49
  if (pluginPos) {
@@ -76,7 +77,8 @@ export class Particle {
76
77
  fixVertical(outModes.top ?? outModes.default);
77
78
  fixVertical(outModes.bottom ?? outModes.default);
78
79
  if (this._checkOverlap(pos, tryCount)) {
79
- return this._calcPosition(container, undefined, zIndex, tryCount + 1);
80
+ const increment = 1;
81
+ return this._calcPosition(container, undefined, zIndex, tryCount + increment);
80
82
  }
81
83
  return pos;
82
84
  };
@@ -85,9 +87,9 @@ export class Particle {
85
87
  if (moveOptions.direction === "inside" || moveOptions.direction === "outside") {
86
88
  return res;
87
89
  }
88
- const rad = (Math.PI / 180) * getRangeValue(moveOptions.angle.value), radOffset = (Math.PI / 180) * getRangeValue(moveOptions.angle.offset), range = {
89
- left: radOffset - rad * 0.5,
90
- right: radOffset + rad * 0.5,
90
+ const rad = degToRad(getRangeValue(moveOptions.angle.value)), radOffset = degToRad(getRangeValue(moveOptions.angle.offset)), range = {
91
+ left: radOffset - rad * half,
92
+ right: radOffset + rad * half,
91
93
  };
92
94
  if (!moveOptions.straight) {
93
95
  res.angle += randomInRange(setRangeValue(range.left, range.right));
@@ -97,7 +99,7 @@ export class Particle {
97
99
  }
98
100
  return res;
99
101
  };
100
- this._checkOverlap = (pos, tryCount = 0) => {
102
+ this._checkOverlap = (pos, tryCount = defaultRetryCount) => {
101
103
  const collisionsOptions = this.options.collisions, radius = this.getRadius();
102
104
  if (!collisionsOptions.enable) {
103
105
  return false;
@@ -106,8 +108,8 @@ export class Particle {
106
108
  if (overlapOptions.enable) {
107
109
  return false;
108
110
  }
109
- const retries = overlapOptions.retries;
110
- if (retries >= 0 && tryCount > retries) {
111
+ const retries = overlapOptions.retries, minRetries = 0;
112
+ if (retries >= minRetries && tryCount > retries) {
111
113
  throw new Error(`${errorPrefix} particle is overlapping and can't be placed`);
112
114
  }
113
115
  return !!this.container.particles.find((particle) => getDistance(pos, particle.position) < radius + particle.getRadius());
@@ -116,7 +118,7 @@ export class Particle {
116
118
  if (!color || !this.roll || (!this.backColor && !this.roll.alter)) {
117
119
  return color;
118
120
  }
119
- const backFactor = this.roll.horizontal && this.roll.vertical ? 2 : 1, backSum = this.roll.horizontal ? Math.PI * 0.5 : 0, rolled = Math.floor(((this.roll.angle ?? 0) + backSum) / (Math.PI / backFactor)) % 2;
121
+ const rollFactor = 1, none = 0, backFactor = this.roll.horizontal && this.roll.vertical ? double * rollFactor : rollFactor, backSum = this.roll.horizontal ? Math.PI * half : none, rolled = Math.floor(((this.roll.angle ?? none) + backSum) / (Math.PI / backFactor)) % double;
120
122
  if (!rolled) {
121
123
  return color;
122
124
  }
@@ -129,13 +131,13 @@ export class Particle {
129
131
  return color;
130
132
  };
131
133
  this._initPosition = (position) => {
132
- const container = this.container, zIndexValue = getRangeValue(this.options.zIndex.value);
133
- this.position = this._calcPosition(container, position, clamp(zIndexValue, 0, container.zLayers));
134
+ const container = this.container, zIndexValue = getRangeValue(this.options.zIndex.value), minZ = 0;
135
+ this.position = this._calcPosition(container, position, clamp(zIndexValue, minZ, container.zLayers));
134
136
  this.initialPosition = this.position.copy();
135
- const canvasSize = container.canvas.size;
137
+ const canvasSize = container.canvas.size, defaultRadius = 0;
136
138
  this.moveCenter = {
137
139
  ...getPosition(this.options.move.center, canvasSize),
138
- radius: this.options.move.center.radius ?? 0,
140
+ radius: this.options.move.center.radius ?? defaultRadius,
139
141
  mode: this.options.move.center.mode ?? "percent",
140
142
  };
141
143
  this.direction = getParticleDirectionAngle(this.options.move.direction, this.position, this.moveCenter);
@@ -160,14 +162,14 @@ export class Particle {
160
162
  this.bubble.inRange = false;
161
163
  this.slow.inRange = false;
162
164
  const container = this.container, pathGenerator = this.pathGenerator, shapeDrawer = container.shapeDrawers.get(this.shape);
163
- shapeDrawer && shapeDrawer.particleDestroy && shapeDrawer.particleDestroy(this);
165
+ shapeDrawer?.particleDestroy?.(this);
164
166
  for (const [, plugin] of container.plugins) {
165
- plugin.particleDestroyed && plugin.particleDestroyed(this, override);
167
+ plugin.particleDestroyed?.(this, override);
166
168
  }
167
169
  for (const updater of container.particles.updaters) {
168
- updater.particleDestroyed && updater.particleDestroyed(this, override);
170
+ updater.particleDestroyed?.(this, override);
169
171
  }
170
- pathGenerator && pathGenerator.reset(this);
172
+ pathGenerator?.reset(this);
171
173
  this._engine.dispatchEvent("particleDestroyed", {
172
174
  container: this.container,
173
175
  data: {
@@ -186,7 +188,7 @@ export class Particle {
186
188
  return this._getRollColor(this.bubble.color ?? getHslFromAnimation(this.color));
187
189
  }
188
190
  getMass() {
189
- return this.getRadius() ** 2 * Math.PI * 0.5;
191
+ return this.getRadius() ** squareExp * Math.PI * half;
190
192
  }
191
193
  getPosition() {
192
194
  return {
@@ -225,14 +227,14 @@ export class Particle {
225
227
  this.shape = itemFromSingleOrMultiple(shapeType, this.id, reduceDuplicates);
226
228
  const effectOptions = particlesOptions.effect, shapeOptions = particlesOptions.shape;
227
229
  if (overrideOptions) {
228
- if (overrideOptions.effect && overrideOptions.effect.type) {
230
+ if (overrideOptions.effect?.type) {
229
231
  const overrideEffectType = overrideOptions.effect.type, effect = itemFromSingleOrMultiple(overrideEffectType, this.id, reduceDuplicates);
230
232
  if (effect) {
231
233
  this.effect = effect;
232
234
  effectOptions.load(overrideOptions.effect);
233
235
  }
234
236
  }
235
- if (overrideOptions.shape && overrideOptions.shape.type) {
237
+ if (overrideOptions.shape?.type) {
236
238
  const overrideShapeType = overrideOptions.shape.type, shape = itemFromSingleOrMultiple(overrideShapeType, this.id, reduceDuplicates);
237
239
  if (shape) {
238
240
  this.shape = shape;
@@ -261,7 +263,7 @@ export class Particle {
261
263
  this.shapeClose = shapeData?.close ?? particlesOptions.shape.close;
262
264
  this.options = particlesOptions;
263
265
  const pathOptions = this.options.move.path;
264
- this.pathDelay = getRangeValue(pathOptions.delay.value) * 1000;
266
+ this.pathDelay = getRangeValue(pathOptions.delay.value) * millisecondsToSeconds;
265
267
  if (pathOptions.generator) {
266
268
  this.pathGenerator = this._engine.getPathGenerator(pathOptions.generator);
267
269
  if (this.pathGenerator && container.addPath(pathOptions.generator, this.pathGenerator)) {
@@ -280,7 +282,8 @@ export class Particle {
280
282
  this._initPosition(position);
281
283
  this.initialVelocity = this._calculateVelocity();
282
284
  this.velocity = this.initialVelocity.copy();
283
- this.moveDecay = 1 - getRangeValue(this.options.move.decay);
285
+ const decayOffset = 1;
286
+ this.moveDecay = decayOffset - getRangeValue(this.options.move.decay);
284
287
  const particles = container.particles;
285
288
  particles.setLastZIndex(this.position.z);
286
289
  this.zIndexFactor = this.position.z / container.zLayers;
@@ -292,7 +295,7 @@ export class Particle {
292
295
  container.effectDrawers.set(this.effect, effectDrawer);
293
296
  }
294
297
  }
295
- if (effectDrawer && effectDrawer.loadEffect) {
298
+ if (effectDrawer?.loadEffect) {
296
299
  effectDrawer.loadEffect(this);
297
300
  }
298
301
  let shapeDrawer = container.shapeDrawers.get(this.shape);
@@ -302,7 +305,7 @@ export class Particle {
302
305
  container.shapeDrawers.set(this.shape, shapeDrawer);
303
306
  }
304
307
  }
305
- if (shapeDrawer && shapeDrawer.loadShape) {
308
+ if (shapeDrawer?.loadShape) {
306
309
  shapeDrawer.loadShape(this);
307
310
  }
308
311
  const sideCountFunc = shapeDrawer?.getSidesCount;
@@ -315,16 +318,12 @@ export class Particle {
315
318
  updater.init(this);
316
319
  }
317
320
  for (const mover of particles.movers) {
318
- mover.init && mover.init(this);
319
- }
320
- if (effectDrawer && effectDrawer.particleInit) {
321
- effectDrawer.particleInit(container, this);
322
- }
323
- if (shapeDrawer && shapeDrawer.particleInit) {
324
- shapeDrawer.particleInit(container, this);
321
+ mover.init?.(this);
325
322
  }
323
+ effectDrawer?.particleInit?.(container, this);
324
+ shapeDrawer?.particleInit?.(container, this);
326
325
  for (const [, plugin] of container.plugins) {
327
- plugin.particleCreated && plugin.particleCreated(this);
326
+ plugin.particleCreated?.(this);
328
327
  }
329
328
  }
330
329
  isInsideCanvas() {
@@ -339,7 +338,7 @@ export class Particle {
339
338
  }
340
339
  reset() {
341
340
  for (const updater of this.container.particles.updaters) {
342
- updater.reset && updater.reset(this);
341
+ updater.reset?.(this);
343
342
  }
344
343
  }
345
344
  }