@tsparticles/interaction-external-repulse 4.0.0-beta.9 → 4.0.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.
@@ -5,6 +5,7 @@ export class RepulseBase {
5
5
  easing;
6
6
  factor;
7
7
  maxSpeed;
8
+ restore;
8
9
  speed;
9
10
  constructor() {
10
11
  this.distance = 200;
@@ -13,6 +14,12 @@ export class RepulseBase {
13
14
  this.speed = 1;
14
15
  this.maxSpeed = 50;
15
16
  this.easing = EasingType.easeOutQuad;
17
+ this.restore = {
18
+ enable: false,
19
+ delay: 0,
20
+ speed: 0.08,
21
+ follow: true,
22
+ };
16
23
  }
17
24
  load(data) {
18
25
  if (isNull(data)) {
@@ -36,5 +43,11 @@ export class RepulseBase {
36
43
  if (data.maxSpeed !== undefined) {
37
44
  this.maxSpeed = data.maxSpeed;
38
45
  }
46
+ if (data.restore !== undefined) {
47
+ this.restore.enable = data.restore.enable ?? this.restore.enable;
48
+ this.restore.delay = data.restore.delay ?? this.restore.delay;
49
+ this.restore.speed = data.restore.speed ?? this.restore.speed;
50
+ this.restore.follow = data.restore.follow ?? this.restore.follow;
51
+ }
39
52
  }
40
53
  }
@@ -1,19 +1,23 @@
1
1
  import { Circle, Rectangle, Vector, clamp, getDistances, half, isInArray, millisecondsToSeconds, safeDocument, } from "@tsparticles/engine";
2
2
  import { DivType, ExternalInteractorBase, divMode, divModeExecute, isDivModeEnabled, mouseMoveEvent, } from "@tsparticles/plugin-interactivity";
3
3
  import { Repulse } from "./Options/Classes/Repulse.js";
4
- const repulseMode = "repulse", minDistance = 0, repulseRadiusFactor = 6, repulseRadiusPower = 3, squarePower = 2, minRadius = 0, minSpeed = 0, easingOffset = 1;
4
+ const repulseMode = "repulse", minDistance = 0, repulseRadiusFactor = 6, repulseRadiusPower = 3, squarePower = 2, minRadius = 0, minSpeed = 0, easingOffset = 1, minRestoreSpeed = 0.001, maxRestoreSpeed = 1, restoreEpsilon = 0.5;
5
5
  export class Repulser extends ExternalInteractorBase {
6
6
  handleClickMode;
7
7
  _clickVec;
8
+ _interactedThisFrame;
8
9
  _maxDistance;
9
10
  _normVec;
10
11
  _pluginManager;
12
+ _restoreData;
11
13
  constructor(pluginManager, container) {
12
14
  super(container);
13
15
  this._pluginManager = pluginManager;
14
16
  this._maxDistance = 0;
15
17
  this._normVec = Vector.origin;
18
+ this._interactedThisFrame = new Set();
16
19
  this._clickVec = Vector.origin;
20
+ this._restoreData = new Map();
17
21
  container.repulse ??= { particles: [] };
18
22
  this.handleClickMode = (mode, interactivityData) => {
19
23
  const options = this.container.actualOptions, repulseOpts = options.interactivity?.modes.repulse;
@@ -54,6 +58,7 @@ export class Repulser extends ExternalInteractorBase {
54
58
  container.retina.repulseModeDistance = repulse.distance * container.retina.pixelRatio;
55
59
  }
56
60
  interact(interactivityData) {
61
+ this._interactedThisFrame.clear();
57
62
  const container = this.container, options = container.actualOptions, mouseMoveStatus = interactivityData.status === mouseMoveEvent, events = options.interactivity?.events;
58
63
  if (!events) {
59
64
  return;
@@ -70,6 +75,7 @@ export class Repulser extends ExternalInteractorBase {
70
75
  this._singleSelectorRepulse(interactivityData, selector, div);
71
76
  });
72
77
  }
78
+ this._restoreParticles();
73
79
  }
74
80
  isEnabled(interactivityData, particle) {
75
81
  const container = this.container, options = container.actualOptions, mouse = interactivityData.mouse, events = (particle?.interactivity ?? options.interactivity)?.events;
@@ -117,6 +123,7 @@ export class Repulser extends ExternalInteractorBase {
117
123
  for (const particle of query) {
118
124
  const { dx, dy, distance } = getDistances(mouseClickPos, particle.position), d = distance ** squarePower, velocity = repulseOptions.speed, force = (-repulseRadius * velocity) / d;
119
125
  if (d <= repulseRadius) {
126
+ this._trackInteractedParticle(particle);
120
127
  repulse.particles.push(particle);
121
128
  this._clickVec.x = dx;
122
129
  this._clickVec.y = dy;
@@ -149,9 +156,46 @@ export class Repulser extends ExternalInteractorBase {
149
156
  const { dx, dy, distance } = getDistances(particle.position, position), repulseFactor = clamp(easingFunc(easingOffset - distance / repulseRadius) * velocity, minSpeed, maxSpeed);
150
157
  this._normVec.x = !distance ? velocity : (dx / distance) * repulseFactor;
151
158
  this._normVec.y = !distance ? velocity : (dy / distance) * repulseFactor;
159
+ this._trackInteractedParticle(particle);
152
160
  particle.position.addTo(this._normVec);
153
161
  }
154
162
  };
163
+ _restoreParticles() {
164
+ const restore = this.container.actualOptions.interactivity?.modes.repulse?.restore;
165
+ if (!restore?.enable || !this._restoreData.size) {
166
+ return;
167
+ }
168
+ const now = Date.now(), restoreDelay = restore.delay * millisecondsToSeconds, restoreSpeed = Math.max(minRestoreSpeed, Math.min(maxRestoreSpeed, restore.speed));
169
+ for (const [particle, restoreData] of this._restoreData) {
170
+ if (this._interactedThisFrame.has(particle)) {
171
+ continue;
172
+ }
173
+ if (particle.destroyed) {
174
+ this._restoreData.delete(particle);
175
+ continue;
176
+ }
177
+ const target = restoreData.target;
178
+ if (now - restoreData.lastInteractionTime < restoreDelay) {
179
+ continue;
180
+ }
181
+ if (restore.follow && particle.options.move.enable) {
182
+ target.x += particle.velocity.x;
183
+ target.y += particle.velocity.y;
184
+ target.z += particle.velocity.z;
185
+ }
186
+ const dx = target.x - particle.position.x, dy = target.y - particle.position.y, dz = target.z - particle.position.z;
187
+ particle.position.x += dx * restoreSpeed;
188
+ particle.position.y += dy * restoreSpeed;
189
+ particle.position.z += dz * restoreSpeed;
190
+ if (Math.abs(dx) <= restoreEpsilon && Math.abs(dy) <= restoreEpsilon) {
191
+ particle.position.x = target.x;
192
+ particle.position.y = target.y;
193
+ particle.position.z = target.z;
194
+ this._restoreData.delete(particle);
195
+ continue;
196
+ }
197
+ }
198
+ }
155
199
  _singleSelectorRepulse = (interactivityData, selector, div) => {
156
200
  const container = this.container, repulse = container.actualOptions.interactivity?.modes.repulse;
157
201
  if (!repulse) {
@@ -171,4 +215,26 @@ export class Repulser extends ExternalInteractorBase {
171
215
  this._processRepulse(interactivityData, pos, repulseRadius, area, divRepulse);
172
216
  });
173
217
  };
218
+ _trackInteractedParticle(particle) {
219
+ this._interactedThisFrame.add(particle);
220
+ const restore = this.container.actualOptions.interactivity?.modes.repulse?.restore;
221
+ if (!restore?.enable) {
222
+ return;
223
+ }
224
+ const now = Date.now();
225
+ let restoreData = this._restoreData.get(particle);
226
+ if (!restoreData) {
227
+ restoreData = {
228
+ target: particle.position.copy(),
229
+ lastInteractionTime: now,
230
+ };
231
+ this._restoreData.set(particle, restoreData);
232
+ }
233
+ restoreData.lastInteractionTime = now;
234
+ if (restore.follow && particle.options.move.enable) {
235
+ restoreData.target.x += particle.velocity.x;
236
+ restoreData.target.y += particle.velocity.y;
237
+ restoreData.target.z += particle.velocity.z;
238
+ }
239
+ }
174
240
  }
@@ -0,0 +1,5 @@
1
+ import { loadExternalRepulseInteraction } from "./index.js";
2
+ const globalObject = globalThis;
3
+ globalObject.__tsParticlesInternals = globalObject.__tsParticlesInternals ?? {};
4
+ globalObject.loadExternalRepulseInteraction = loadExternalRepulseInteraction;
5
+ export * from "./index.js";
package/browser/index.js CHANGED
@@ -1,12 +1,12 @@
1
+ import { ensureInteractivityPluginLoaded } from "@tsparticles/plugin-interactivity";
2
+ import { Repulser } from "./Repulser.js";
1
3
  export async function loadExternalRepulseInteraction(engine) {
2
- engine.checkVersion("4.0.0-beta.9");
3
- await engine.pluginManager.register(async (e) => {
4
- const { ensureInteractivityPluginLoaded } = await import("@tsparticles/plugin-interactivity");
4
+ engine.checkVersion("4.0.0");
5
+ await engine.pluginManager.register((e) => {
5
6
  ensureInteractivityPluginLoaded(e);
6
7
  const pluginManager = e.pluginManager;
7
- pluginManager.addInteractor?.("externalRepulse", async (container) => {
8
- const { Repulser } = await import("./Repulser.js");
9
- return new Repulser(pluginManager, container);
8
+ pluginManager.addInteractor?.("externalRepulse", container => {
9
+ return Promise.resolve(new Repulser(pluginManager, container));
10
10
  });
11
11
  });
12
12
  }
@@ -0,0 +1,15 @@
1
+ export async function loadExternalRepulseInteraction(engine) {
2
+ engine.checkVersion("4.0.0");
3
+ await engine.pluginManager.register(async (e) => {
4
+ const { ensureInteractivityPluginLoaded } = await import("@tsparticles/plugin-interactivity/lazy");
5
+ ensureInteractivityPluginLoaded(e);
6
+ const pluginManager = e.pluginManager;
7
+ pluginManager.addInteractor?.("externalRepulse", async (container) => {
8
+ const { Repulser } = await import("./Repulser.js");
9
+ return new Repulser(pluginManager, container);
10
+ });
11
+ });
12
+ }
13
+ export * from "./Options/Classes/RepulseBase.js";
14
+ export * from "./Options/Classes/RepulseDiv.js";
15
+ export * from "./Options/Classes/Repulse.js";
@@ -5,6 +5,7 @@ export class RepulseBase {
5
5
  easing;
6
6
  factor;
7
7
  maxSpeed;
8
+ restore;
8
9
  speed;
9
10
  constructor() {
10
11
  this.distance = 200;
@@ -13,6 +14,12 @@ export class RepulseBase {
13
14
  this.speed = 1;
14
15
  this.maxSpeed = 50;
15
16
  this.easing = EasingType.easeOutQuad;
17
+ this.restore = {
18
+ enable: false,
19
+ delay: 0,
20
+ speed: 0.08,
21
+ follow: true,
22
+ };
16
23
  }
17
24
  load(data) {
18
25
  if (isNull(data)) {
@@ -36,5 +43,11 @@ export class RepulseBase {
36
43
  if (data.maxSpeed !== undefined) {
37
44
  this.maxSpeed = data.maxSpeed;
38
45
  }
46
+ if (data.restore !== undefined) {
47
+ this.restore.enable = data.restore.enable ?? this.restore.enable;
48
+ this.restore.delay = data.restore.delay ?? this.restore.delay;
49
+ this.restore.speed = data.restore.speed ?? this.restore.speed;
50
+ this.restore.follow = data.restore.follow ?? this.restore.follow;
51
+ }
39
52
  }
40
53
  }
package/cjs/Repulser.js CHANGED
@@ -1,19 +1,23 @@
1
1
  import { Circle, Rectangle, Vector, clamp, getDistances, half, isInArray, millisecondsToSeconds, safeDocument, } from "@tsparticles/engine";
2
2
  import { DivType, ExternalInteractorBase, divMode, divModeExecute, isDivModeEnabled, mouseMoveEvent, } from "@tsparticles/plugin-interactivity";
3
3
  import { Repulse } from "./Options/Classes/Repulse.js";
4
- const repulseMode = "repulse", minDistance = 0, repulseRadiusFactor = 6, repulseRadiusPower = 3, squarePower = 2, minRadius = 0, minSpeed = 0, easingOffset = 1;
4
+ const repulseMode = "repulse", minDistance = 0, repulseRadiusFactor = 6, repulseRadiusPower = 3, squarePower = 2, minRadius = 0, minSpeed = 0, easingOffset = 1, minRestoreSpeed = 0.001, maxRestoreSpeed = 1, restoreEpsilon = 0.5;
5
5
  export class Repulser extends ExternalInteractorBase {
6
6
  handleClickMode;
7
7
  _clickVec;
8
+ _interactedThisFrame;
8
9
  _maxDistance;
9
10
  _normVec;
10
11
  _pluginManager;
12
+ _restoreData;
11
13
  constructor(pluginManager, container) {
12
14
  super(container);
13
15
  this._pluginManager = pluginManager;
14
16
  this._maxDistance = 0;
15
17
  this._normVec = Vector.origin;
18
+ this._interactedThisFrame = new Set();
16
19
  this._clickVec = Vector.origin;
20
+ this._restoreData = new Map();
17
21
  container.repulse ??= { particles: [] };
18
22
  this.handleClickMode = (mode, interactivityData) => {
19
23
  const options = this.container.actualOptions, repulseOpts = options.interactivity?.modes.repulse;
@@ -54,6 +58,7 @@ export class Repulser extends ExternalInteractorBase {
54
58
  container.retina.repulseModeDistance = repulse.distance * container.retina.pixelRatio;
55
59
  }
56
60
  interact(interactivityData) {
61
+ this._interactedThisFrame.clear();
57
62
  const container = this.container, options = container.actualOptions, mouseMoveStatus = interactivityData.status === mouseMoveEvent, events = options.interactivity?.events;
58
63
  if (!events) {
59
64
  return;
@@ -70,6 +75,7 @@ export class Repulser extends ExternalInteractorBase {
70
75
  this._singleSelectorRepulse(interactivityData, selector, div);
71
76
  });
72
77
  }
78
+ this._restoreParticles();
73
79
  }
74
80
  isEnabled(interactivityData, particle) {
75
81
  const container = this.container, options = container.actualOptions, mouse = interactivityData.mouse, events = (particle?.interactivity ?? options.interactivity)?.events;
@@ -117,6 +123,7 @@ export class Repulser extends ExternalInteractorBase {
117
123
  for (const particle of query) {
118
124
  const { dx, dy, distance } = getDistances(mouseClickPos, particle.position), d = distance ** squarePower, velocity = repulseOptions.speed, force = (-repulseRadius * velocity) / d;
119
125
  if (d <= repulseRadius) {
126
+ this._trackInteractedParticle(particle);
120
127
  repulse.particles.push(particle);
121
128
  this._clickVec.x = dx;
122
129
  this._clickVec.y = dy;
@@ -149,9 +156,46 @@ export class Repulser extends ExternalInteractorBase {
149
156
  const { dx, dy, distance } = getDistances(particle.position, position), repulseFactor = clamp(easingFunc(easingOffset - distance / repulseRadius) * velocity, minSpeed, maxSpeed);
150
157
  this._normVec.x = !distance ? velocity : (dx / distance) * repulseFactor;
151
158
  this._normVec.y = !distance ? velocity : (dy / distance) * repulseFactor;
159
+ this._trackInteractedParticle(particle);
152
160
  particle.position.addTo(this._normVec);
153
161
  }
154
162
  };
163
+ _restoreParticles() {
164
+ const restore = this.container.actualOptions.interactivity?.modes.repulse?.restore;
165
+ if (!restore?.enable || !this._restoreData.size) {
166
+ return;
167
+ }
168
+ const now = Date.now(), restoreDelay = restore.delay * millisecondsToSeconds, restoreSpeed = Math.max(minRestoreSpeed, Math.min(maxRestoreSpeed, restore.speed));
169
+ for (const [particle, restoreData] of this._restoreData) {
170
+ if (this._interactedThisFrame.has(particle)) {
171
+ continue;
172
+ }
173
+ if (particle.destroyed) {
174
+ this._restoreData.delete(particle);
175
+ continue;
176
+ }
177
+ const target = restoreData.target;
178
+ if (now - restoreData.lastInteractionTime < restoreDelay) {
179
+ continue;
180
+ }
181
+ if (restore.follow && particle.options.move.enable) {
182
+ target.x += particle.velocity.x;
183
+ target.y += particle.velocity.y;
184
+ target.z += particle.velocity.z;
185
+ }
186
+ const dx = target.x - particle.position.x, dy = target.y - particle.position.y, dz = target.z - particle.position.z;
187
+ particle.position.x += dx * restoreSpeed;
188
+ particle.position.y += dy * restoreSpeed;
189
+ particle.position.z += dz * restoreSpeed;
190
+ if (Math.abs(dx) <= restoreEpsilon && Math.abs(dy) <= restoreEpsilon) {
191
+ particle.position.x = target.x;
192
+ particle.position.y = target.y;
193
+ particle.position.z = target.z;
194
+ this._restoreData.delete(particle);
195
+ continue;
196
+ }
197
+ }
198
+ }
155
199
  _singleSelectorRepulse = (interactivityData, selector, div) => {
156
200
  const container = this.container, repulse = container.actualOptions.interactivity?.modes.repulse;
157
201
  if (!repulse) {
@@ -171,4 +215,26 @@ export class Repulser extends ExternalInteractorBase {
171
215
  this._processRepulse(interactivityData, pos, repulseRadius, area, divRepulse);
172
216
  });
173
217
  };
218
+ _trackInteractedParticle(particle) {
219
+ this._interactedThisFrame.add(particle);
220
+ const restore = this.container.actualOptions.interactivity?.modes.repulse?.restore;
221
+ if (!restore?.enable) {
222
+ return;
223
+ }
224
+ const now = Date.now();
225
+ let restoreData = this._restoreData.get(particle);
226
+ if (!restoreData) {
227
+ restoreData = {
228
+ target: particle.position.copy(),
229
+ lastInteractionTime: now,
230
+ };
231
+ this._restoreData.set(particle, restoreData);
232
+ }
233
+ restoreData.lastInteractionTime = now;
234
+ if (restore.follow && particle.options.move.enable) {
235
+ restoreData.target.x += particle.velocity.x;
236
+ restoreData.target.y += particle.velocity.y;
237
+ restoreData.target.z += particle.velocity.z;
238
+ }
239
+ }
174
240
  }
package/cjs/browser.js ADDED
@@ -0,0 +1,5 @@
1
+ import { loadExternalRepulseInteraction } from "./index.js";
2
+ const globalObject = globalThis;
3
+ globalObject.__tsParticlesInternals = globalObject.__tsParticlesInternals ?? {};
4
+ globalObject.loadExternalRepulseInteraction = loadExternalRepulseInteraction;
5
+ export * from "./index.js";
package/cjs/index.js CHANGED
@@ -1,12 +1,12 @@
1
+ import { ensureInteractivityPluginLoaded } from "@tsparticles/plugin-interactivity";
2
+ import { Repulser } from "./Repulser.js";
1
3
  export async function loadExternalRepulseInteraction(engine) {
2
- engine.checkVersion("4.0.0-beta.9");
3
- await engine.pluginManager.register(async (e) => {
4
- const { ensureInteractivityPluginLoaded } = await import("@tsparticles/plugin-interactivity");
4
+ engine.checkVersion("4.0.0");
5
+ await engine.pluginManager.register((e) => {
5
6
  ensureInteractivityPluginLoaded(e);
6
7
  const pluginManager = e.pluginManager;
7
- pluginManager.addInteractor?.("externalRepulse", async (container) => {
8
- const { Repulser } = await import("./Repulser.js");
9
- return new Repulser(pluginManager, container);
8
+ pluginManager.addInteractor?.("externalRepulse", container => {
9
+ return Promise.resolve(new Repulser(pluginManager, container));
10
10
  });
11
11
  });
12
12
  }
@@ -0,0 +1,15 @@
1
+ export async function loadExternalRepulseInteraction(engine) {
2
+ engine.checkVersion("4.0.0");
3
+ await engine.pluginManager.register(async (e) => {
4
+ const { ensureInteractivityPluginLoaded } = await import("@tsparticles/plugin-interactivity/lazy");
5
+ ensureInteractivityPluginLoaded(e);
6
+ const pluginManager = e.pluginManager;
7
+ pluginManager.addInteractor?.("externalRepulse", async (container) => {
8
+ const { Repulser } = await import("./Repulser.js");
9
+ return new Repulser(pluginManager, container);
10
+ });
11
+ });
12
+ }
13
+ export * from "./Options/Classes/RepulseBase.js";
14
+ export * from "./Options/Classes/RepulseDiv.js";
15
+ export * from "./Options/Classes/Repulse.js";
@@ -5,6 +5,7 @@ export class RepulseBase {
5
5
  easing;
6
6
  factor;
7
7
  maxSpeed;
8
+ restore;
8
9
  speed;
9
10
  constructor() {
10
11
  this.distance = 200;
@@ -13,6 +14,12 @@ export class RepulseBase {
13
14
  this.speed = 1;
14
15
  this.maxSpeed = 50;
15
16
  this.easing = EasingType.easeOutQuad;
17
+ this.restore = {
18
+ enable: false,
19
+ delay: 0,
20
+ speed: 0.08,
21
+ follow: true,
22
+ };
16
23
  }
17
24
  load(data) {
18
25
  if (isNull(data)) {
@@ -36,5 +43,11 @@ export class RepulseBase {
36
43
  if (data.maxSpeed !== undefined) {
37
44
  this.maxSpeed = data.maxSpeed;
38
45
  }
46
+ if (data.restore !== undefined) {
47
+ this.restore.enable = data.restore.enable ?? this.restore.enable;
48
+ this.restore.delay = data.restore.delay ?? this.restore.delay;
49
+ this.restore.speed = data.restore.speed ?? this.restore.speed;
50
+ this.restore.follow = data.restore.follow ?? this.restore.follow;
51
+ }
39
52
  }
40
53
  }
package/esm/Repulser.js CHANGED
@@ -1,19 +1,23 @@
1
1
  import { Circle, Rectangle, Vector, clamp, getDistances, half, isInArray, millisecondsToSeconds, safeDocument, } from "@tsparticles/engine";
2
2
  import { DivType, ExternalInteractorBase, divMode, divModeExecute, isDivModeEnabled, mouseMoveEvent, } from "@tsparticles/plugin-interactivity";
3
3
  import { Repulse } from "./Options/Classes/Repulse.js";
4
- const repulseMode = "repulse", minDistance = 0, repulseRadiusFactor = 6, repulseRadiusPower = 3, squarePower = 2, minRadius = 0, minSpeed = 0, easingOffset = 1;
4
+ const repulseMode = "repulse", minDistance = 0, repulseRadiusFactor = 6, repulseRadiusPower = 3, squarePower = 2, minRadius = 0, minSpeed = 0, easingOffset = 1, minRestoreSpeed = 0.001, maxRestoreSpeed = 1, restoreEpsilon = 0.5;
5
5
  export class Repulser extends ExternalInteractorBase {
6
6
  handleClickMode;
7
7
  _clickVec;
8
+ _interactedThisFrame;
8
9
  _maxDistance;
9
10
  _normVec;
10
11
  _pluginManager;
12
+ _restoreData;
11
13
  constructor(pluginManager, container) {
12
14
  super(container);
13
15
  this._pluginManager = pluginManager;
14
16
  this._maxDistance = 0;
15
17
  this._normVec = Vector.origin;
18
+ this._interactedThisFrame = new Set();
16
19
  this._clickVec = Vector.origin;
20
+ this._restoreData = new Map();
17
21
  container.repulse ??= { particles: [] };
18
22
  this.handleClickMode = (mode, interactivityData) => {
19
23
  const options = this.container.actualOptions, repulseOpts = options.interactivity?.modes.repulse;
@@ -54,6 +58,7 @@ export class Repulser extends ExternalInteractorBase {
54
58
  container.retina.repulseModeDistance = repulse.distance * container.retina.pixelRatio;
55
59
  }
56
60
  interact(interactivityData) {
61
+ this._interactedThisFrame.clear();
57
62
  const container = this.container, options = container.actualOptions, mouseMoveStatus = interactivityData.status === mouseMoveEvent, events = options.interactivity?.events;
58
63
  if (!events) {
59
64
  return;
@@ -70,6 +75,7 @@ export class Repulser extends ExternalInteractorBase {
70
75
  this._singleSelectorRepulse(interactivityData, selector, div);
71
76
  });
72
77
  }
78
+ this._restoreParticles();
73
79
  }
74
80
  isEnabled(interactivityData, particle) {
75
81
  const container = this.container, options = container.actualOptions, mouse = interactivityData.mouse, events = (particle?.interactivity ?? options.interactivity)?.events;
@@ -117,6 +123,7 @@ export class Repulser extends ExternalInteractorBase {
117
123
  for (const particle of query) {
118
124
  const { dx, dy, distance } = getDistances(mouseClickPos, particle.position), d = distance ** squarePower, velocity = repulseOptions.speed, force = (-repulseRadius * velocity) / d;
119
125
  if (d <= repulseRadius) {
126
+ this._trackInteractedParticle(particle);
120
127
  repulse.particles.push(particle);
121
128
  this._clickVec.x = dx;
122
129
  this._clickVec.y = dy;
@@ -149,9 +156,46 @@ export class Repulser extends ExternalInteractorBase {
149
156
  const { dx, dy, distance } = getDistances(particle.position, position), repulseFactor = clamp(easingFunc(easingOffset - distance / repulseRadius) * velocity, minSpeed, maxSpeed);
150
157
  this._normVec.x = !distance ? velocity : (dx / distance) * repulseFactor;
151
158
  this._normVec.y = !distance ? velocity : (dy / distance) * repulseFactor;
159
+ this._trackInteractedParticle(particle);
152
160
  particle.position.addTo(this._normVec);
153
161
  }
154
162
  };
163
+ _restoreParticles() {
164
+ const restore = this.container.actualOptions.interactivity?.modes.repulse?.restore;
165
+ if (!restore?.enable || !this._restoreData.size) {
166
+ return;
167
+ }
168
+ const now = Date.now(), restoreDelay = restore.delay * millisecondsToSeconds, restoreSpeed = Math.max(minRestoreSpeed, Math.min(maxRestoreSpeed, restore.speed));
169
+ for (const [particle, restoreData] of this._restoreData) {
170
+ if (this._interactedThisFrame.has(particle)) {
171
+ continue;
172
+ }
173
+ if (particle.destroyed) {
174
+ this._restoreData.delete(particle);
175
+ continue;
176
+ }
177
+ const target = restoreData.target;
178
+ if (now - restoreData.lastInteractionTime < restoreDelay) {
179
+ continue;
180
+ }
181
+ if (restore.follow && particle.options.move.enable) {
182
+ target.x += particle.velocity.x;
183
+ target.y += particle.velocity.y;
184
+ target.z += particle.velocity.z;
185
+ }
186
+ const dx = target.x - particle.position.x, dy = target.y - particle.position.y, dz = target.z - particle.position.z;
187
+ particle.position.x += dx * restoreSpeed;
188
+ particle.position.y += dy * restoreSpeed;
189
+ particle.position.z += dz * restoreSpeed;
190
+ if (Math.abs(dx) <= restoreEpsilon && Math.abs(dy) <= restoreEpsilon) {
191
+ particle.position.x = target.x;
192
+ particle.position.y = target.y;
193
+ particle.position.z = target.z;
194
+ this._restoreData.delete(particle);
195
+ continue;
196
+ }
197
+ }
198
+ }
155
199
  _singleSelectorRepulse = (interactivityData, selector, div) => {
156
200
  const container = this.container, repulse = container.actualOptions.interactivity?.modes.repulse;
157
201
  if (!repulse) {
@@ -171,4 +215,26 @@ export class Repulser extends ExternalInteractorBase {
171
215
  this._processRepulse(interactivityData, pos, repulseRadius, area, divRepulse);
172
216
  });
173
217
  };
218
+ _trackInteractedParticle(particle) {
219
+ this._interactedThisFrame.add(particle);
220
+ const restore = this.container.actualOptions.interactivity?.modes.repulse?.restore;
221
+ if (!restore?.enable) {
222
+ return;
223
+ }
224
+ const now = Date.now();
225
+ let restoreData = this._restoreData.get(particle);
226
+ if (!restoreData) {
227
+ restoreData = {
228
+ target: particle.position.copy(),
229
+ lastInteractionTime: now,
230
+ };
231
+ this._restoreData.set(particle, restoreData);
232
+ }
233
+ restoreData.lastInteractionTime = now;
234
+ if (restore.follow && particle.options.move.enable) {
235
+ restoreData.target.x += particle.velocity.x;
236
+ restoreData.target.y += particle.velocity.y;
237
+ restoreData.target.z += particle.velocity.z;
238
+ }
239
+ }
174
240
  }
package/esm/browser.js ADDED
@@ -0,0 +1,5 @@
1
+ import { loadExternalRepulseInteraction } from "./index.js";
2
+ const globalObject = globalThis;
3
+ globalObject.__tsParticlesInternals = globalObject.__tsParticlesInternals ?? {};
4
+ globalObject.loadExternalRepulseInteraction = loadExternalRepulseInteraction;
5
+ export * from "./index.js";
package/esm/index.js CHANGED
@@ -1,12 +1,12 @@
1
+ import { ensureInteractivityPluginLoaded } from "@tsparticles/plugin-interactivity";
2
+ import { Repulser } from "./Repulser.js";
1
3
  export async function loadExternalRepulseInteraction(engine) {
2
- engine.checkVersion("4.0.0-beta.9");
3
- await engine.pluginManager.register(async (e) => {
4
- const { ensureInteractivityPluginLoaded } = await import("@tsparticles/plugin-interactivity");
4
+ engine.checkVersion("4.0.0");
5
+ await engine.pluginManager.register((e) => {
5
6
  ensureInteractivityPluginLoaded(e);
6
7
  const pluginManager = e.pluginManager;
7
- pluginManager.addInteractor?.("externalRepulse", async (container) => {
8
- const { Repulser } = await import("./Repulser.js");
9
- return new Repulser(pluginManager, container);
8
+ pluginManager.addInteractor?.("externalRepulse", container => {
9
+ return Promise.resolve(new Repulser(pluginManager, container));
10
10
  });
11
11
  });
12
12
  }
@@ -0,0 +1,15 @@
1
+ export async function loadExternalRepulseInteraction(engine) {
2
+ engine.checkVersion("4.0.0");
3
+ await engine.pluginManager.register(async (e) => {
4
+ const { ensureInteractivityPluginLoaded } = await import("@tsparticles/plugin-interactivity/lazy");
5
+ ensureInteractivityPluginLoaded(e);
6
+ const pluginManager = e.pluginManager;
7
+ pluginManager.addInteractor?.("externalRepulse", async (container) => {
8
+ const { Repulser } = await import("./Repulser.js");
9
+ return new Repulser(pluginManager, container);
10
+ });
11
+ });
12
+ }
13
+ export * from "./Options/Classes/RepulseBase.js";
14
+ export * from "./Options/Classes/RepulseDiv.js";
15
+ export * from "./Options/Classes/Repulse.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tsparticles/interaction-external-repulse",
3
- "version": "4.0.0-beta.9",
3
+ "version": "4.0.0",
4
4
  "description": "tsParticles repulse external interaction",
5
5
  "homepage": "https://particles.js.org",
6
6
  "repository": {
@@ -83,6 +83,13 @@
83
83
  "require": "./cjs/index.js",
84
84
  "default": "./esm/index.js"
85
85
  },
86
+ "./lazy": {
87
+ "types": "./types/index.lazy.d.ts",
88
+ "browser": "./browser/index.lazy.js",
89
+ "import": "./esm/index.lazy.js",
90
+ "require": "./cjs/index.lazy.js",
91
+ "default": "./esm/index.lazy.js"
92
+ },
86
93
  "./package.json": "./package.json"
87
94
  },
88
95
  "peerDepdendencies": {
@@ -94,7 +101,7 @@
94
101
  },
95
102
  "type": "module",
96
103
  "peerDependencies": {
97
- "@tsparticles/engine": "4.0.0-beta.9",
98
- "@tsparticles/plugin-interactivity": "4.0.0-beta.9"
104
+ "@tsparticles/engine": "4.0.0",
105
+ "@tsparticles/plugin-interactivity": "4.0.0"
99
106
  }
100
107
  }