@tsparticles/interaction-external-attract 4.0.0-beta.15 → 4.0.0-beta.17
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/browser/Attractor.js +70 -3
- package/browser/Options/Classes/Attract.js +13 -0
- package/browser/Utils.js +6 -5
- package/browser/index.js +1 -1
- package/browser/index.lazy.js +1 -1
- package/cjs/Attractor.js +70 -3
- package/cjs/Options/Classes/Attract.js +13 -0
- package/cjs/Utils.js +6 -5
- package/cjs/index.js +1 -1
- package/cjs/index.lazy.js +1 -1
- package/esm/Attractor.js +70 -3
- package/esm/Options/Classes/Attract.js +13 -0
- package/esm/Utils.js +6 -5
- package/esm/index.js +1 -1
- package/esm/index.lazy.js +1 -1
- package/package.json +3 -3
- package/report.html +1 -1
- package/tsparticles.interaction.external.attract.js +91 -10
- package/tsparticles.interaction.external.attract.min.js +1 -1
- package/types/Attractor.d.ts +4 -0
- package/types/Options/Classes/Attract.d.ts +2 -1
- package/types/Options/Interfaces/IAttract.d.ts +7 -0
- package/types/Types.d.ts +1 -2
- package/types/Utils.d.ts +2 -2
package/browser/Attractor.js
CHANGED
|
@@ -2,15 +2,19 @@ import { ExternalInteractorBase, mouseMoveEvent, } from "@tsparticles/plugin-int
|
|
|
2
2
|
import { isInArray, millisecondsToSeconds } from "@tsparticles/engine";
|
|
3
3
|
import { clickAttract, hoverAttract } from "./Utils.js";
|
|
4
4
|
import { Attract } from "./Options/Classes/Attract.js";
|
|
5
|
-
const attractMode = "attract";
|
|
5
|
+
const attractMode = "attract", minVelocityLengthSq = 0, minRestoreSpeed = 0.001, maxRestoreSpeed = 1, restoreEpsilon = 0.5;
|
|
6
6
|
export class Attractor extends ExternalInteractorBase {
|
|
7
7
|
handleClickMode;
|
|
8
|
+
_interactedThisFrame;
|
|
8
9
|
_maxDistance;
|
|
9
10
|
_pluginManager;
|
|
11
|
+
_restoreData;
|
|
10
12
|
constructor(pluginManager, container) {
|
|
11
13
|
super(container);
|
|
12
14
|
this._pluginManager = pluginManager;
|
|
13
15
|
this._maxDistance = 0;
|
|
16
|
+
this._interactedThisFrame = new Set();
|
|
17
|
+
this._restoreData = new Map();
|
|
14
18
|
container.attract ??= { particles: [] };
|
|
15
19
|
this.handleClickMode = (mode, interactivityData) => {
|
|
16
20
|
const options = this.container.actualOptions, attract = options.interactivity?.modes.attract;
|
|
@@ -51,17 +55,23 @@ export class Attractor extends ExternalInteractorBase {
|
|
|
51
55
|
container.retina.attractModeDistance = attract.distance * container.retina.pixelRatio;
|
|
52
56
|
}
|
|
53
57
|
interact(interactivityData) {
|
|
58
|
+
this._interactedThisFrame.clear();
|
|
54
59
|
const container = this.container, options = container.actualOptions, mouseMoveStatus = interactivityData.status === mouseMoveEvent, events = options.interactivity?.events;
|
|
55
60
|
if (!events) {
|
|
56
61
|
return;
|
|
57
62
|
}
|
|
58
63
|
const { enable: hoverEnabled, mode: hoverMode } = events.onHover, { enable: clickEnabled, mode: clickMode } = events.onClick;
|
|
59
64
|
if (mouseMoveStatus && hoverEnabled && isInArray(attractMode, hoverMode)) {
|
|
60
|
-
hoverAttract(this._pluginManager, this.container, interactivityData, p => this.isEnabled(interactivityData, p)
|
|
65
|
+
hoverAttract(this._pluginManager, this.container, interactivityData, p => this.isEnabled(interactivityData, p), p => {
|
|
66
|
+
this._trackInteractedParticle(p);
|
|
67
|
+
});
|
|
61
68
|
}
|
|
62
69
|
else if (clickEnabled && isInArray(attractMode, clickMode)) {
|
|
63
|
-
clickAttract(this._pluginManager, this.container, interactivityData, p => this.isEnabled(interactivityData, p)
|
|
70
|
+
clickAttract(this._pluginManager, this.container, interactivityData, p => this.isEnabled(interactivityData, p), p => {
|
|
71
|
+
this._trackInteractedParticle(p);
|
|
72
|
+
});
|
|
64
73
|
}
|
|
74
|
+
this._restoreParticles();
|
|
65
75
|
}
|
|
66
76
|
isEnabled(interactivityData, particle) {
|
|
67
77
|
const container = this.container, options = container.actualOptions, mouse = interactivityData.mouse, events = (particle?.interactivity ?? options.interactivity)?.events;
|
|
@@ -79,4 +89,61 @@ export class Attractor extends ExternalInteractorBase {
|
|
|
79
89
|
}
|
|
80
90
|
reset() {
|
|
81
91
|
}
|
|
92
|
+
_restoreParticles() {
|
|
93
|
+
const restore = this.container.actualOptions.interactivity?.modes.attract?.restore;
|
|
94
|
+
if (!restore?.enable || !this._restoreData.size) {
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
const now = Date.now(), restoreDelay = restore.delay * millisecondsToSeconds, restoreSpeed = Math.max(minRestoreSpeed, Math.min(maxRestoreSpeed, restore.speed));
|
|
98
|
+
for (const [particle, restoreData] of this._restoreData) {
|
|
99
|
+
if (this._interactedThisFrame.has(particle)) {
|
|
100
|
+
continue;
|
|
101
|
+
}
|
|
102
|
+
if (particle.destroyed) {
|
|
103
|
+
this._restoreData.delete(particle);
|
|
104
|
+
continue;
|
|
105
|
+
}
|
|
106
|
+
const target = restoreData.target;
|
|
107
|
+
if (now - restoreData.lastInteractionTime < restoreDelay) {
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
let dx = target.x - particle.position.x, dy = target.y - particle.position.y, dz = target.z - particle.position.z;
|
|
111
|
+
if (restore.follow && particle.options.move.enable) {
|
|
112
|
+
const { x: vx, y: vy, z: vz } = particle.velocity, velocityLengthSq = vx * vx + vy * vy + vz * vz;
|
|
113
|
+
if (velocityLengthSq > minVelocityLengthSq) {
|
|
114
|
+
const parallelScale = (dx * vx + dy * vy + dz * vz) / velocityLengthSq;
|
|
115
|
+
dx -= vx * parallelScale;
|
|
116
|
+
dy -= vy * parallelScale;
|
|
117
|
+
dz -= vz * parallelScale;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
particle.position.x += dx * restoreSpeed;
|
|
121
|
+
particle.position.y += dy * restoreSpeed;
|
|
122
|
+
particle.position.z += dz * restoreSpeed;
|
|
123
|
+
if (Math.abs(dx) <= restoreEpsilon && Math.abs(dy) <= restoreEpsilon) {
|
|
124
|
+
particle.position.x = target.x;
|
|
125
|
+
particle.position.y = target.y;
|
|
126
|
+
particle.position.z = target.z;
|
|
127
|
+
this._restoreData.delete(particle);
|
|
128
|
+
continue;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
_trackInteractedParticle(particle) {
|
|
133
|
+
this._interactedThisFrame.add(particle);
|
|
134
|
+
const restore = this.container.actualOptions.interactivity?.modes.attract?.restore;
|
|
135
|
+
if (!restore?.enable) {
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
const now = Date.now();
|
|
139
|
+
let restoreData = this._restoreData.get(particle);
|
|
140
|
+
if (!restoreData) {
|
|
141
|
+
restoreData = {
|
|
142
|
+
target: particle.position.copy(),
|
|
143
|
+
lastInteractionTime: now,
|
|
144
|
+
};
|
|
145
|
+
this._restoreData.set(particle, restoreData);
|
|
146
|
+
}
|
|
147
|
+
restoreData.lastInteractionTime = now;
|
|
148
|
+
}
|
|
82
149
|
}
|
|
@@ -5,6 +5,7 @@ export class Attract {
|
|
|
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 Attract {
|
|
|
13
14
|
this.factor = 1;
|
|
14
15
|
this.maxSpeed = 50;
|
|
15
16
|
this.speed = 1;
|
|
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 Attract {
|
|
|
36
43
|
if (data.speed !== undefined) {
|
|
37
44
|
this.speed = data.speed;
|
|
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/browser/Utils.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Circle, Vector, clamp, getDistances, identity, } from "@tsparticles/engine";
|
|
2
2
|
const minFactor = 1, minRadius = 0, updateVector = Vector.origin;
|
|
3
|
-
function processAttract(pluginManager, container, position, attractRadius, area, queryCb) {
|
|
3
|
+
function processAttract(pluginManager, container, position, attractRadius, area, queryCb, onAttractParticle) {
|
|
4
4
|
const attractOptions = container.actualOptions.interactivity?.modes.attract;
|
|
5
5
|
if (!attractOptions) {
|
|
6
6
|
return;
|
|
@@ -10,10 +10,11 @@ function processAttract(pluginManager, container, position, attractRadius, area,
|
|
|
10
10
|
const { dx, dy, distance } = getDistances(particle.position, position), velocity = attractOptions.speed * attractOptions.factor, attractFactor = clamp(pluginManager.getEasing(attractOptions.easing)(identity - distance / attractRadius) * velocity, minFactor, attractOptions.maxSpeed);
|
|
11
11
|
updateVector.x = !distance ? velocity : (dx / distance) * attractFactor;
|
|
12
12
|
updateVector.y = !distance ? velocity : (dy / distance) * attractFactor;
|
|
13
|
+
onAttractParticle?.(particle);
|
|
13
14
|
particle.position.subFrom(updateVector);
|
|
14
15
|
}
|
|
15
16
|
}
|
|
16
|
-
export function clickAttract(pluginManager, container, interactivityData, enabledCb) {
|
|
17
|
+
export function clickAttract(pluginManager, container, interactivityData, enabledCb, onAttractParticle) {
|
|
17
18
|
container.attract ??= { particles: [] };
|
|
18
19
|
const { attract } = container;
|
|
19
20
|
if (!attract.finish) {
|
|
@@ -28,16 +29,16 @@ export function clickAttract(pluginManager, container, interactivityData, enable
|
|
|
28
29
|
if (!attractRadius || attractRadius < minRadius || !mousePos) {
|
|
29
30
|
return;
|
|
30
31
|
}
|
|
31
|
-
processAttract(pluginManager, container, mousePos, attractRadius, new Circle(mousePos.x, mousePos.y, attractRadius), (p) => enabledCb(p));
|
|
32
|
+
processAttract(pluginManager, container, mousePos, attractRadius, new Circle(mousePos.x, mousePos.y, attractRadius), (p) => enabledCb(p), onAttractParticle);
|
|
32
33
|
}
|
|
33
34
|
else if (attract.clicking === false) {
|
|
34
35
|
attract.particles = [];
|
|
35
36
|
}
|
|
36
37
|
}
|
|
37
|
-
export function hoverAttract(pluginManager, container, interactivityData, enabledCb) {
|
|
38
|
+
export function hoverAttract(pluginManager, container, interactivityData, enabledCb, onAttractParticle) {
|
|
38
39
|
const mousePos = interactivityData.mouse.position, attractRadius = container.retina.attractModeDistance;
|
|
39
40
|
if (!attractRadius || attractRadius < minRadius || !mousePos) {
|
|
40
41
|
return;
|
|
41
42
|
}
|
|
42
|
-
processAttract(pluginManager, container, mousePos, attractRadius, new Circle(mousePos.x, mousePos.y, attractRadius), (p) => enabledCb(p));
|
|
43
|
+
processAttract(pluginManager, container, mousePos, attractRadius, new Circle(mousePos.x, mousePos.y, attractRadius), (p) => enabledCb(p), onAttractParticle);
|
|
43
44
|
}
|
package/browser/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { ensureInteractivityPluginLoaded } from "@tsparticles/plugin-interactivity";
|
|
2
2
|
import { Attractor } from "./Attractor.js";
|
|
3
3
|
export async function loadExternalAttractInteraction(engine) {
|
|
4
|
-
engine.checkVersion("4.0.0-beta.
|
|
4
|
+
engine.checkVersion("4.0.0-beta.17");
|
|
5
5
|
await engine.pluginManager.register((e) => {
|
|
6
6
|
ensureInteractivityPluginLoaded(e);
|
|
7
7
|
e.pluginManager.addInteractor?.("externalAttract", container => {
|
package/browser/index.lazy.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export async function loadExternalAttractInteraction(engine) {
|
|
2
|
-
engine.checkVersion("4.0.0-beta.
|
|
2
|
+
engine.checkVersion("4.0.0-beta.17");
|
|
3
3
|
await engine.pluginManager.register(async (e) => {
|
|
4
4
|
const { ensureInteractivityPluginLoaded } = await import("@tsparticles/plugin-interactivity/lazy");
|
|
5
5
|
ensureInteractivityPluginLoaded(e);
|
package/cjs/Attractor.js
CHANGED
|
@@ -2,15 +2,19 @@ import { ExternalInteractorBase, mouseMoveEvent, } from "@tsparticles/plugin-int
|
|
|
2
2
|
import { isInArray, millisecondsToSeconds } from "@tsparticles/engine";
|
|
3
3
|
import { clickAttract, hoverAttract } from "./Utils.js";
|
|
4
4
|
import { Attract } from "./Options/Classes/Attract.js";
|
|
5
|
-
const attractMode = "attract";
|
|
5
|
+
const attractMode = "attract", minVelocityLengthSq = 0, minRestoreSpeed = 0.001, maxRestoreSpeed = 1, restoreEpsilon = 0.5;
|
|
6
6
|
export class Attractor extends ExternalInteractorBase {
|
|
7
7
|
handleClickMode;
|
|
8
|
+
_interactedThisFrame;
|
|
8
9
|
_maxDistance;
|
|
9
10
|
_pluginManager;
|
|
11
|
+
_restoreData;
|
|
10
12
|
constructor(pluginManager, container) {
|
|
11
13
|
super(container);
|
|
12
14
|
this._pluginManager = pluginManager;
|
|
13
15
|
this._maxDistance = 0;
|
|
16
|
+
this._interactedThisFrame = new Set();
|
|
17
|
+
this._restoreData = new Map();
|
|
14
18
|
container.attract ??= { particles: [] };
|
|
15
19
|
this.handleClickMode = (mode, interactivityData) => {
|
|
16
20
|
const options = this.container.actualOptions, attract = options.interactivity?.modes.attract;
|
|
@@ -51,17 +55,23 @@ export class Attractor extends ExternalInteractorBase {
|
|
|
51
55
|
container.retina.attractModeDistance = attract.distance * container.retina.pixelRatio;
|
|
52
56
|
}
|
|
53
57
|
interact(interactivityData) {
|
|
58
|
+
this._interactedThisFrame.clear();
|
|
54
59
|
const container = this.container, options = container.actualOptions, mouseMoveStatus = interactivityData.status === mouseMoveEvent, events = options.interactivity?.events;
|
|
55
60
|
if (!events) {
|
|
56
61
|
return;
|
|
57
62
|
}
|
|
58
63
|
const { enable: hoverEnabled, mode: hoverMode } = events.onHover, { enable: clickEnabled, mode: clickMode } = events.onClick;
|
|
59
64
|
if (mouseMoveStatus && hoverEnabled && isInArray(attractMode, hoverMode)) {
|
|
60
|
-
hoverAttract(this._pluginManager, this.container, interactivityData, p => this.isEnabled(interactivityData, p)
|
|
65
|
+
hoverAttract(this._pluginManager, this.container, interactivityData, p => this.isEnabled(interactivityData, p), p => {
|
|
66
|
+
this._trackInteractedParticle(p);
|
|
67
|
+
});
|
|
61
68
|
}
|
|
62
69
|
else if (clickEnabled && isInArray(attractMode, clickMode)) {
|
|
63
|
-
clickAttract(this._pluginManager, this.container, interactivityData, p => this.isEnabled(interactivityData, p)
|
|
70
|
+
clickAttract(this._pluginManager, this.container, interactivityData, p => this.isEnabled(interactivityData, p), p => {
|
|
71
|
+
this._trackInteractedParticle(p);
|
|
72
|
+
});
|
|
64
73
|
}
|
|
74
|
+
this._restoreParticles();
|
|
65
75
|
}
|
|
66
76
|
isEnabled(interactivityData, particle) {
|
|
67
77
|
const container = this.container, options = container.actualOptions, mouse = interactivityData.mouse, events = (particle?.interactivity ?? options.interactivity)?.events;
|
|
@@ -79,4 +89,61 @@ export class Attractor extends ExternalInteractorBase {
|
|
|
79
89
|
}
|
|
80
90
|
reset() {
|
|
81
91
|
}
|
|
92
|
+
_restoreParticles() {
|
|
93
|
+
const restore = this.container.actualOptions.interactivity?.modes.attract?.restore;
|
|
94
|
+
if (!restore?.enable || !this._restoreData.size) {
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
const now = Date.now(), restoreDelay = restore.delay * millisecondsToSeconds, restoreSpeed = Math.max(minRestoreSpeed, Math.min(maxRestoreSpeed, restore.speed));
|
|
98
|
+
for (const [particle, restoreData] of this._restoreData) {
|
|
99
|
+
if (this._interactedThisFrame.has(particle)) {
|
|
100
|
+
continue;
|
|
101
|
+
}
|
|
102
|
+
if (particle.destroyed) {
|
|
103
|
+
this._restoreData.delete(particle);
|
|
104
|
+
continue;
|
|
105
|
+
}
|
|
106
|
+
const target = restoreData.target;
|
|
107
|
+
if (now - restoreData.lastInteractionTime < restoreDelay) {
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
let dx = target.x - particle.position.x, dy = target.y - particle.position.y, dz = target.z - particle.position.z;
|
|
111
|
+
if (restore.follow && particle.options.move.enable) {
|
|
112
|
+
const { x: vx, y: vy, z: vz } = particle.velocity, velocityLengthSq = vx * vx + vy * vy + vz * vz;
|
|
113
|
+
if (velocityLengthSq > minVelocityLengthSq) {
|
|
114
|
+
const parallelScale = (dx * vx + dy * vy + dz * vz) / velocityLengthSq;
|
|
115
|
+
dx -= vx * parallelScale;
|
|
116
|
+
dy -= vy * parallelScale;
|
|
117
|
+
dz -= vz * parallelScale;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
particle.position.x += dx * restoreSpeed;
|
|
121
|
+
particle.position.y += dy * restoreSpeed;
|
|
122
|
+
particle.position.z += dz * restoreSpeed;
|
|
123
|
+
if (Math.abs(dx) <= restoreEpsilon && Math.abs(dy) <= restoreEpsilon) {
|
|
124
|
+
particle.position.x = target.x;
|
|
125
|
+
particle.position.y = target.y;
|
|
126
|
+
particle.position.z = target.z;
|
|
127
|
+
this._restoreData.delete(particle);
|
|
128
|
+
continue;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
_trackInteractedParticle(particle) {
|
|
133
|
+
this._interactedThisFrame.add(particle);
|
|
134
|
+
const restore = this.container.actualOptions.interactivity?.modes.attract?.restore;
|
|
135
|
+
if (!restore?.enable) {
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
const now = Date.now();
|
|
139
|
+
let restoreData = this._restoreData.get(particle);
|
|
140
|
+
if (!restoreData) {
|
|
141
|
+
restoreData = {
|
|
142
|
+
target: particle.position.copy(),
|
|
143
|
+
lastInteractionTime: now,
|
|
144
|
+
};
|
|
145
|
+
this._restoreData.set(particle, restoreData);
|
|
146
|
+
}
|
|
147
|
+
restoreData.lastInteractionTime = now;
|
|
148
|
+
}
|
|
82
149
|
}
|
|
@@ -5,6 +5,7 @@ export class Attract {
|
|
|
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 Attract {
|
|
|
13
14
|
this.factor = 1;
|
|
14
15
|
this.maxSpeed = 50;
|
|
15
16
|
this.speed = 1;
|
|
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 Attract {
|
|
|
36
43
|
if (data.speed !== undefined) {
|
|
37
44
|
this.speed = data.speed;
|
|
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/Utils.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Circle, Vector, clamp, getDistances, identity, } from "@tsparticles/engine";
|
|
2
2
|
const minFactor = 1, minRadius = 0, updateVector = Vector.origin;
|
|
3
|
-
function processAttract(pluginManager, container, position, attractRadius, area, queryCb) {
|
|
3
|
+
function processAttract(pluginManager, container, position, attractRadius, area, queryCb, onAttractParticle) {
|
|
4
4
|
const attractOptions = container.actualOptions.interactivity?.modes.attract;
|
|
5
5
|
if (!attractOptions) {
|
|
6
6
|
return;
|
|
@@ -10,10 +10,11 @@ function processAttract(pluginManager, container, position, attractRadius, area,
|
|
|
10
10
|
const { dx, dy, distance } = getDistances(particle.position, position), velocity = attractOptions.speed * attractOptions.factor, attractFactor = clamp(pluginManager.getEasing(attractOptions.easing)(identity - distance / attractRadius) * velocity, minFactor, attractOptions.maxSpeed);
|
|
11
11
|
updateVector.x = !distance ? velocity : (dx / distance) * attractFactor;
|
|
12
12
|
updateVector.y = !distance ? velocity : (dy / distance) * attractFactor;
|
|
13
|
+
onAttractParticle?.(particle);
|
|
13
14
|
particle.position.subFrom(updateVector);
|
|
14
15
|
}
|
|
15
16
|
}
|
|
16
|
-
export function clickAttract(pluginManager, container, interactivityData, enabledCb) {
|
|
17
|
+
export function clickAttract(pluginManager, container, interactivityData, enabledCb, onAttractParticle) {
|
|
17
18
|
container.attract ??= { particles: [] };
|
|
18
19
|
const { attract } = container;
|
|
19
20
|
if (!attract.finish) {
|
|
@@ -28,16 +29,16 @@ export function clickAttract(pluginManager, container, interactivityData, enable
|
|
|
28
29
|
if (!attractRadius || attractRadius < minRadius || !mousePos) {
|
|
29
30
|
return;
|
|
30
31
|
}
|
|
31
|
-
processAttract(pluginManager, container, mousePos, attractRadius, new Circle(mousePos.x, mousePos.y, attractRadius), (p) => enabledCb(p));
|
|
32
|
+
processAttract(pluginManager, container, mousePos, attractRadius, new Circle(mousePos.x, mousePos.y, attractRadius), (p) => enabledCb(p), onAttractParticle);
|
|
32
33
|
}
|
|
33
34
|
else if (attract.clicking === false) {
|
|
34
35
|
attract.particles = [];
|
|
35
36
|
}
|
|
36
37
|
}
|
|
37
|
-
export function hoverAttract(pluginManager, container, interactivityData, enabledCb) {
|
|
38
|
+
export function hoverAttract(pluginManager, container, interactivityData, enabledCb, onAttractParticle) {
|
|
38
39
|
const mousePos = interactivityData.mouse.position, attractRadius = container.retina.attractModeDistance;
|
|
39
40
|
if (!attractRadius || attractRadius < minRadius || !mousePos) {
|
|
40
41
|
return;
|
|
41
42
|
}
|
|
42
|
-
processAttract(pluginManager, container, mousePos, attractRadius, new Circle(mousePos.x, mousePos.y, attractRadius), (p) => enabledCb(p));
|
|
43
|
+
processAttract(pluginManager, container, mousePos, attractRadius, new Circle(mousePos.x, mousePos.y, attractRadius), (p) => enabledCb(p), onAttractParticle);
|
|
43
44
|
}
|
package/cjs/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { ensureInteractivityPluginLoaded } from "@tsparticles/plugin-interactivity";
|
|
2
2
|
import { Attractor } from "./Attractor.js";
|
|
3
3
|
export async function loadExternalAttractInteraction(engine) {
|
|
4
|
-
engine.checkVersion("4.0.0-beta.
|
|
4
|
+
engine.checkVersion("4.0.0-beta.17");
|
|
5
5
|
await engine.pluginManager.register((e) => {
|
|
6
6
|
ensureInteractivityPluginLoaded(e);
|
|
7
7
|
e.pluginManager.addInteractor?.("externalAttract", container => {
|
package/cjs/index.lazy.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export async function loadExternalAttractInteraction(engine) {
|
|
2
|
-
engine.checkVersion("4.0.0-beta.
|
|
2
|
+
engine.checkVersion("4.0.0-beta.17");
|
|
3
3
|
await engine.pluginManager.register(async (e) => {
|
|
4
4
|
const { ensureInteractivityPluginLoaded } = await import("@tsparticles/plugin-interactivity/lazy");
|
|
5
5
|
ensureInteractivityPluginLoaded(e);
|
package/esm/Attractor.js
CHANGED
|
@@ -2,15 +2,19 @@ import { ExternalInteractorBase, mouseMoveEvent, } from "@tsparticles/plugin-int
|
|
|
2
2
|
import { isInArray, millisecondsToSeconds } from "@tsparticles/engine";
|
|
3
3
|
import { clickAttract, hoverAttract } from "./Utils.js";
|
|
4
4
|
import { Attract } from "./Options/Classes/Attract.js";
|
|
5
|
-
const attractMode = "attract";
|
|
5
|
+
const attractMode = "attract", minVelocityLengthSq = 0, minRestoreSpeed = 0.001, maxRestoreSpeed = 1, restoreEpsilon = 0.5;
|
|
6
6
|
export class Attractor extends ExternalInteractorBase {
|
|
7
7
|
handleClickMode;
|
|
8
|
+
_interactedThisFrame;
|
|
8
9
|
_maxDistance;
|
|
9
10
|
_pluginManager;
|
|
11
|
+
_restoreData;
|
|
10
12
|
constructor(pluginManager, container) {
|
|
11
13
|
super(container);
|
|
12
14
|
this._pluginManager = pluginManager;
|
|
13
15
|
this._maxDistance = 0;
|
|
16
|
+
this._interactedThisFrame = new Set();
|
|
17
|
+
this._restoreData = new Map();
|
|
14
18
|
container.attract ??= { particles: [] };
|
|
15
19
|
this.handleClickMode = (mode, interactivityData) => {
|
|
16
20
|
const options = this.container.actualOptions, attract = options.interactivity?.modes.attract;
|
|
@@ -51,17 +55,23 @@ export class Attractor extends ExternalInteractorBase {
|
|
|
51
55
|
container.retina.attractModeDistance = attract.distance * container.retina.pixelRatio;
|
|
52
56
|
}
|
|
53
57
|
interact(interactivityData) {
|
|
58
|
+
this._interactedThisFrame.clear();
|
|
54
59
|
const container = this.container, options = container.actualOptions, mouseMoveStatus = interactivityData.status === mouseMoveEvent, events = options.interactivity?.events;
|
|
55
60
|
if (!events) {
|
|
56
61
|
return;
|
|
57
62
|
}
|
|
58
63
|
const { enable: hoverEnabled, mode: hoverMode } = events.onHover, { enable: clickEnabled, mode: clickMode } = events.onClick;
|
|
59
64
|
if (mouseMoveStatus && hoverEnabled && isInArray(attractMode, hoverMode)) {
|
|
60
|
-
hoverAttract(this._pluginManager, this.container, interactivityData, p => this.isEnabled(interactivityData, p)
|
|
65
|
+
hoverAttract(this._pluginManager, this.container, interactivityData, p => this.isEnabled(interactivityData, p), p => {
|
|
66
|
+
this._trackInteractedParticle(p);
|
|
67
|
+
});
|
|
61
68
|
}
|
|
62
69
|
else if (clickEnabled && isInArray(attractMode, clickMode)) {
|
|
63
|
-
clickAttract(this._pluginManager, this.container, interactivityData, p => this.isEnabled(interactivityData, p)
|
|
70
|
+
clickAttract(this._pluginManager, this.container, interactivityData, p => this.isEnabled(interactivityData, p), p => {
|
|
71
|
+
this._trackInteractedParticle(p);
|
|
72
|
+
});
|
|
64
73
|
}
|
|
74
|
+
this._restoreParticles();
|
|
65
75
|
}
|
|
66
76
|
isEnabled(interactivityData, particle) {
|
|
67
77
|
const container = this.container, options = container.actualOptions, mouse = interactivityData.mouse, events = (particle?.interactivity ?? options.interactivity)?.events;
|
|
@@ -79,4 +89,61 @@ export class Attractor extends ExternalInteractorBase {
|
|
|
79
89
|
}
|
|
80
90
|
reset() {
|
|
81
91
|
}
|
|
92
|
+
_restoreParticles() {
|
|
93
|
+
const restore = this.container.actualOptions.interactivity?.modes.attract?.restore;
|
|
94
|
+
if (!restore?.enable || !this._restoreData.size) {
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
const now = Date.now(), restoreDelay = restore.delay * millisecondsToSeconds, restoreSpeed = Math.max(minRestoreSpeed, Math.min(maxRestoreSpeed, restore.speed));
|
|
98
|
+
for (const [particle, restoreData] of this._restoreData) {
|
|
99
|
+
if (this._interactedThisFrame.has(particle)) {
|
|
100
|
+
continue;
|
|
101
|
+
}
|
|
102
|
+
if (particle.destroyed) {
|
|
103
|
+
this._restoreData.delete(particle);
|
|
104
|
+
continue;
|
|
105
|
+
}
|
|
106
|
+
const target = restoreData.target;
|
|
107
|
+
if (now - restoreData.lastInteractionTime < restoreDelay) {
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
let dx = target.x - particle.position.x, dy = target.y - particle.position.y, dz = target.z - particle.position.z;
|
|
111
|
+
if (restore.follow && particle.options.move.enable) {
|
|
112
|
+
const { x: vx, y: vy, z: vz } = particle.velocity, velocityLengthSq = vx * vx + vy * vy + vz * vz;
|
|
113
|
+
if (velocityLengthSq > minVelocityLengthSq) {
|
|
114
|
+
const parallelScale = (dx * vx + dy * vy + dz * vz) / velocityLengthSq;
|
|
115
|
+
dx -= vx * parallelScale;
|
|
116
|
+
dy -= vy * parallelScale;
|
|
117
|
+
dz -= vz * parallelScale;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
particle.position.x += dx * restoreSpeed;
|
|
121
|
+
particle.position.y += dy * restoreSpeed;
|
|
122
|
+
particle.position.z += dz * restoreSpeed;
|
|
123
|
+
if (Math.abs(dx) <= restoreEpsilon && Math.abs(dy) <= restoreEpsilon) {
|
|
124
|
+
particle.position.x = target.x;
|
|
125
|
+
particle.position.y = target.y;
|
|
126
|
+
particle.position.z = target.z;
|
|
127
|
+
this._restoreData.delete(particle);
|
|
128
|
+
continue;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
_trackInteractedParticle(particle) {
|
|
133
|
+
this._interactedThisFrame.add(particle);
|
|
134
|
+
const restore = this.container.actualOptions.interactivity?.modes.attract?.restore;
|
|
135
|
+
if (!restore?.enable) {
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
const now = Date.now();
|
|
139
|
+
let restoreData = this._restoreData.get(particle);
|
|
140
|
+
if (!restoreData) {
|
|
141
|
+
restoreData = {
|
|
142
|
+
target: particle.position.copy(),
|
|
143
|
+
lastInteractionTime: now,
|
|
144
|
+
};
|
|
145
|
+
this._restoreData.set(particle, restoreData);
|
|
146
|
+
}
|
|
147
|
+
restoreData.lastInteractionTime = now;
|
|
148
|
+
}
|
|
82
149
|
}
|
|
@@ -5,6 +5,7 @@ export class Attract {
|
|
|
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 Attract {
|
|
|
13
14
|
this.factor = 1;
|
|
14
15
|
this.maxSpeed = 50;
|
|
15
16
|
this.speed = 1;
|
|
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 Attract {
|
|
|
36
43
|
if (data.speed !== undefined) {
|
|
37
44
|
this.speed = data.speed;
|
|
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/Utils.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Circle, Vector, clamp, getDistances, identity, } from "@tsparticles/engine";
|
|
2
2
|
const minFactor = 1, minRadius = 0, updateVector = Vector.origin;
|
|
3
|
-
function processAttract(pluginManager, container, position, attractRadius, area, queryCb) {
|
|
3
|
+
function processAttract(pluginManager, container, position, attractRadius, area, queryCb, onAttractParticle) {
|
|
4
4
|
const attractOptions = container.actualOptions.interactivity?.modes.attract;
|
|
5
5
|
if (!attractOptions) {
|
|
6
6
|
return;
|
|
@@ -10,10 +10,11 @@ function processAttract(pluginManager, container, position, attractRadius, area,
|
|
|
10
10
|
const { dx, dy, distance } = getDistances(particle.position, position), velocity = attractOptions.speed * attractOptions.factor, attractFactor = clamp(pluginManager.getEasing(attractOptions.easing)(identity - distance / attractRadius) * velocity, minFactor, attractOptions.maxSpeed);
|
|
11
11
|
updateVector.x = !distance ? velocity : (dx / distance) * attractFactor;
|
|
12
12
|
updateVector.y = !distance ? velocity : (dy / distance) * attractFactor;
|
|
13
|
+
onAttractParticle?.(particle);
|
|
13
14
|
particle.position.subFrom(updateVector);
|
|
14
15
|
}
|
|
15
16
|
}
|
|
16
|
-
export function clickAttract(pluginManager, container, interactivityData, enabledCb) {
|
|
17
|
+
export function clickAttract(pluginManager, container, interactivityData, enabledCb, onAttractParticle) {
|
|
17
18
|
container.attract ??= { particles: [] };
|
|
18
19
|
const { attract } = container;
|
|
19
20
|
if (!attract.finish) {
|
|
@@ -28,16 +29,16 @@ export function clickAttract(pluginManager, container, interactivityData, enable
|
|
|
28
29
|
if (!attractRadius || attractRadius < minRadius || !mousePos) {
|
|
29
30
|
return;
|
|
30
31
|
}
|
|
31
|
-
processAttract(pluginManager, container, mousePos, attractRadius, new Circle(mousePos.x, mousePos.y, attractRadius), (p) => enabledCb(p));
|
|
32
|
+
processAttract(pluginManager, container, mousePos, attractRadius, new Circle(mousePos.x, mousePos.y, attractRadius), (p) => enabledCb(p), onAttractParticle);
|
|
32
33
|
}
|
|
33
34
|
else if (attract.clicking === false) {
|
|
34
35
|
attract.particles = [];
|
|
35
36
|
}
|
|
36
37
|
}
|
|
37
|
-
export function hoverAttract(pluginManager, container, interactivityData, enabledCb) {
|
|
38
|
+
export function hoverAttract(pluginManager, container, interactivityData, enabledCb, onAttractParticle) {
|
|
38
39
|
const mousePos = interactivityData.mouse.position, attractRadius = container.retina.attractModeDistance;
|
|
39
40
|
if (!attractRadius || attractRadius < minRadius || !mousePos) {
|
|
40
41
|
return;
|
|
41
42
|
}
|
|
42
|
-
processAttract(pluginManager, container, mousePos, attractRadius, new Circle(mousePos.x, mousePos.y, attractRadius), (p) => enabledCb(p));
|
|
43
|
+
processAttract(pluginManager, container, mousePos, attractRadius, new Circle(mousePos.x, mousePos.y, attractRadius), (p) => enabledCb(p), onAttractParticle);
|
|
43
44
|
}
|
package/esm/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { ensureInteractivityPluginLoaded } from "@tsparticles/plugin-interactivity";
|
|
2
2
|
import { Attractor } from "./Attractor.js";
|
|
3
3
|
export async function loadExternalAttractInteraction(engine) {
|
|
4
|
-
engine.checkVersion("4.0.0-beta.
|
|
4
|
+
engine.checkVersion("4.0.0-beta.17");
|
|
5
5
|
await engine.pluginManager.register((e) => {
|
|
6
6
|
ensureInteractivityPluginLoaded(e);
|
|
7
7
|
e.pluginManager.addInteractor?.("externalAttract", container => {
|
package/esm/index.lazy.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export async function loadExternalAttractInteraction(engine) {
|
|
2
|
-
engine.checkVersion("4.0.0-beta.
|
|
2
|
+
engine.checkVersion("4.0.0-beta.17");
|
|
3
3
|
await engine.pluginManager.register(async (e) => {
|
|
4
4
|
const { ensureInteractivityPluginLoaded } = await import("@tsparticles/plugin-interactivity/lazy");
|
|
5
5
|
ensureInteractivityPluginLoaded(e);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tsparticles/interaction-external-attract",
|
|
3
|
-
"version": "4.0.0-beta.
|
|
3
|
+
"version": "4.0.0-beta.17",
|
|
4
4
|
"description": "tsParticles attract external interaction",
|
|
5
5
|
"homepage": "https://particles.js.org",
|
|
6
6
|
"repository": {
|
|
@@ -101,7 +101,7 @@
|
|
|
101
101
|
},
|
|
102
102
|
"type": "module",
|
|
103
103
|
"peerDependencies": {
|
|
104
|
-
"@tsparticles/engine": "4.0.0-beta.
|
|
105
|
-
"@tsparticles/plugin-interactivity": "4.0.0-beta.
|
|
104
|
+
"@tsparticles/engine": "4.0.0-beta.17",
|
|
105
|
+
"@tsparticles/plugin-interactivity": "4.0.0-beta.17"
|
|
106
106
|
}
|
|
107
107
|
}
|
package/report.html
CHANGED
|
@@ -4930,7 +4930,7 @@ var drawChart = (function (exports) {
|
|
|
4930
4930
|
</script>
|
|
4931
4931
|
<script>
|
|
4932
4932
|
/*<!--*/
|
|
4933
|
-
const data = {"version":2,"tree":{"name":"root","children":[{"name":"tsparticles.interaction.external.attract.js","children":[{"name":"dist/browser","children":[{"uid":"
|
|
4933
|
+
const data = {"version":2,"tree":{"name":"root","children":[{"name":"tsparticles.interaction.external.attract.js","children":[{"name":"dist/browser","children":[{"uid":"b8e72f39-1","name":"Utils.js"},{"name":"Options/Classes/Attract.js","uid":"b8e72f39-3"},{"uid":"b8e72f39-5","name":"Attractor.js"},{"uid":"b8e72f39-7","name":"index.js"},{"uid":"b8e72f39-9","name":"browser.js"}]}]}],"isRoot":true},"nodeParts":{"b8e72f39-1":{"renderedLength":2506,"gzipLength":0,"brotliLength":0,"metaUid":"b8e72f39-0"},"b8e72f39-3":{"renderedLength":1666,"gzipLength":0,"brotliLength":0,"metaUid":"b8e72f39-2"},"b8e72f39-5":{"renderedLength":7074,"gzipLength":0,"brotliLength":0,"metaUid":"b8e72f39-4"},"b8e72f39-7":{"renderedLength":421,"gzipLength":0,"brotliLength":0,"metaUid":"b8e72f39-6"},"b8e72f39-9":{"renderedLength":203,"gzipLength":0,"brotliLength":0,"metaUid":"b8e72f39-8"}},"nodeMetas":{"b8e72f39-0":{"id":"/dist/browser/Utils.js","moduleParts":{"tsparticles.interaction.external.attract.js":"b8e72f39-1"},"imported":[{"uid":"b8e72f39-11"}],"importedBy":[{"uid":"b8e72f39-4"}]},"b8e72f39-2":{"id":"/dist/browser/Options/Classes/Attract.js","moduleParts":{"tsparticles.interaction.external.attract.js":"b8e72f39-3"},"imported":[{"uid":"b8e72f39-11"}],"importedBy":[{"uid":"b8e72f39-6"},{"uid":"b8e72f39-4"}]},"b8e72f39-4":{"id":"/dist/browser/Attractor.js","moduleParts":{"tsparticles.interaction.external.attract.js":"b8e72f39-5"},"imported":[{"uid":"b8e72f39-10"},{"uid":"b8e72f39-11"},{"uid":"b8e72f39-0"},{"uid":"b8e72f39-2"}],"importedBy":[{"uid":"b8e72f39-6"}]},"b8e72f39-6":{"id":"/dist/browser/index.js","moduleParts":{"tsparticles.interaction.external.attract.js":"b8e72f39-7"},"imported":[{"uid":"b8e72f39-10"},{"uid":"b8e72f39-4"},{"uid":"b8e72f39-2"}],"importedBy":[{"uid":"b8e72f39-8"}]},"b8e72f39-8":{"id":"/dist/browser/browser.js","moduleParts":{"tsparticles.interaction.external.attract.js":"b8e72f39-9"},"imported":[{"uid":"b8e72f39-6"}],"importedBy":[],"isEntry":true},"b8e72f39-10":{"id":"@tsparticles/plugin-interactivity","moduleParts":{},"imported":[],"importedBy":[{"uid":"b8e72f39-6"},{"uid":"b8e72f39-4"}],"isExternal":true},"b8e72f39-11":{"id":"@tsparticles/engine","moduleParts":{},"imported":[],"importedBy":[{"uid":"b8e72f39-4"},{"uid":"b8e72f39-2"},{"uid":"b8e72f39-0"}],"isExternal":true}},"env":{"rollup":"4.60.3"},"options":{"gzip":false,"brotli":false,"sourcemap":false}};
|
|
4934
4934
|
|
|
4935
4935
|
const run = () => {
|
|
4936
4936
|
const width = window.innerWidth;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
(function(g){g.__tsParticlesInternals=g.__tsParticlesInternals||{};g.__tsParticlesInternals.bundles=g.__tsParticlesInternals.bundles||{};g.__tsParticlesInternals.effects=g.__tsParticlesInternals.effects||{};g.__tsParticlesInternals.engine=g.__tsParticlesInternals.engine||{};g.__tsParticlesInternals.interactions=g.__tsParticlesInternals.interactions||{};g.__tsParticlesInternals.palettes=g.__tsParticlesInternals.palettes||{};g.__tsParticlesInternals.paths=g.__tsParticlesInternals.paths||{};g.__tsParticlesInternals.plugins=g.__tsParticlesInternals.plugins||{};g.__tsParticlesInternals.plugins=g.__tsParticlesInternals.plugins||{};g.__tsParticlesInternals.plugins.emittersShapes=g.__tsParticlesInternals.plugins.emittersShapes||{};g.__tsParticlesInternals.presets=g.__tsParticlesInternals.presets||{};g.__tsParticlesInternals.shapes=g.__tsParticlesInternals.shapes||{};g.__tsParticlesInternals.updaters=g.__tsParticlesInternals.updaters||{};g.__tsParticlesInternals.utils=g.__tsParticlesInternals.utils||{};g.__tsParticlesInternals.canvas=g.__tsParticlesInternals.canvas||{};g.__tsParticlesInternals.canvas=g.__tsParticlesInternals.canvas||{};g.__tsParticlesInternals.canvas.utils=g.__tsParticlesInternals.canvas.utils||{};g.__tsParticlesInternals.path=g.__tsParticlesInternals.path||{};g.__tsParticlesInternals.path=g.__tsParticlesInternals.path||{};g.__tsParticlesInternals.path.utils=g.__tsParticlesInternals.path.utils||{};var __tsProxyFactory=typeof Proxy!=="undefined"?function(obj){return new Proxy(obj,{get:function(target,key){if(!(key in target)){target[key]={};}return target[key];}});}:function(obj){return obj;};g.__tsParticlesInternals.bundles=__tsProxyFactory(g.__tsParticlesInternals.bundles);g.__tsParticlesInternals.effects=__tsProxyFactory(g.__tsParticlesInternals.effects);g.__tsParticlesInternals.interactions=__tsProxyFactory(g.__tsParticlesInternals.interactions);g.__tsParticlesInternals.palettes=__tsProxyFactory(g.__tsParticlesInternals.palettes);g.__tsParticlesInternals.paths=__tsProxyFactory(g.__tsParticlesInternals.paths);g.__tsParticlesInternals.plugins=__tsProxyFactory(g.__tsParticlesInternals.plugins);g.__tsParticlesInternals.plugins.emittersShapes=__tsProxyFactory(g.__tsParticlesInternals.plugins.emittersShapes);g.__tsParticlesInternals.presets=__tsProxyFactory(g.__tsParticlesInternals.presets);g.__tsParticlesInternals.shapes=__tsProxyFactory(g.__tsParticlesInternals.shapes);g.__tsParticlesInternals.updaters=__tsProxyFactory(g.__tsParticlesInternals.updaters);g.__tsParticlesInternals.utils=__tsProxyFactory(g.__tsParticlesInternals.utils);g.__tsParticlesInternals.canvas=__tsProxyFactory(g.__tsParticlesInternals.canvas);g.__tsParticlesInternals.path=__tsProxyFactory(g.__tsParticlesInternals.path);g.tsparticlesInternalExports=g.tsparticlesInternalExports||{};})(typeof globalThis!=="undefined"?globalThis:typeof window!=="undefined"?window:this);
|
|
2
|
-
/* External Interaction v4.0.0-beta.
|
|
2
|
+
/* External Interaction v4.0.0-beta.17 */
|
|
3
3
|
(function (global, factory) {
|
|
4
4
|
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('@tsparticles/plugin-interactivity'), require('@tsparticles/engine')) :
|
|
5
5
|
typeof define === 'function' && define.amd ? define(['exports', '@tsparticles/plugin-interactivity', '@tsparticles/engine'], factory) :
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
})(this, (function (exports, pluginInteractivity, engine) { 'use strict';
|
|
8
8
|
|
|
9
9
|
const minFactor = 1, minRadius = 0, updateVector = engine.Vector.origin;
|
|
10
|
-
function processAttract(pluginManager, container, position, attractRadius, area, queryCb) {
|
|
10
|
+
function processAttract(pluginManager, container, position, attractRadius, area, queryCb, onAttractParticle) {
|
|
11
11
|
const attractOptions = container.actualOptions.interactivity?.modes.attract;
|
|
12
12
|
if (!attractOptions) {
|
|
13
13
|
return;
|
|
@@ -17,10 +17,11 @@
|
|
|
17
17
|
const { dx, dy, distance } = engine.getDistances(particle.position, position), velocity = attractOptions.speed * attractOptions.factor, attractFactor = engine.clamp(pluginManager.getEasing(attractOptions.easing)(engine.identity - distance / attractRadius) * velocity, minFactor, attractOptions.maxSpeed);
|
|
18
18
|
updateVector.x = !distance ? velocity : (dx / distance) * attractFactor;
|
|
19
19
|
updateVector.y = !distance ? velocity : (dy / distance) * attractFactor;
|
|
20
|
+
onAttractParticle?.(particle);
|
|
20
21
|
particle.position.subFrom(updateVector);
|
|
21
22
|
}
|
|
22
23
|
}
|
|
23
|
-
function clickAttract(pluginManager, container, interactivityData, enabledCb) {
|
|
24
|
+
function clickAttract(pluginManager, container, interactivityData, enabledCb, onAttractParticle) {
|
|
24
25
|
container.attract ??= { particles: [] };
|
|
25
26
|
const { attract } = container;
|
|
26
27
|
if (!attract.finish) {
|
|
@@ -35,18 +36,18 @@
|
|
|
35
36
|
if (!attractRadius || attractRadius < minRadius || !mousePos) {
|
|
36
37
|
return;
|
|
37
38
|
}
|
|
38
|
-
processAttract(pluginManager, container, mousePos, attractRadius, new engine.Circle(mousePos.x, mousePos.y, attractRadius), (p) => enabledCb(p));
|
|
39
|
+
processAttract(pluginManager, container, mousePos, attractRadius, new engine.Circle(mousePos.x, mousePos.y, attractRadius), (p) => enabledCb(p), onAttractParticle);
|
|
39
40
|
}
|
|
40
41
|
else if (attract.clicking === false) {
|
|
41
42
|
attract.particles = [];
|
|
42
43
|
}
|
|
43
44
|
}
|
|
44
|
-
function hoverAttract(pluginManager, container, interactivityData, enabledCb) {
|
|
45
|
+
function hoverAttract(pluginManager, container, interactivityData, enabledCb, onAttractParticle) {
|
|
45
46
|
const mousePos = interactivityData.mouse.position, attractRadius = container.retina.attractModeDistance;
|
|
46
47
|
if (!attractRadius || attractRadius < minRadius || !mousePos) {
|
|
47
48
|
return;
|
|
48
49
|
}
|
|
49
|
-
processAttract(pluginManager, container, mousePos, attractRadius, new engine.Circle(mousePos.x, mousePos.y, attractRadius), (p) => enabledCb(p));
|
|
50
|
+
processAttract(pluginManager, container, mousePos, attractRadius, new engine.Circle(mousePos.x, mousePos.y, attractRadius), (p) => enabledCb(p), onAttractParticle);
|
|
50
51
|
}
|
|
51
52
|
|
|
52
53
|
class Attract {
|
|
@@ -55,6 +56,7 @@
|
|
|
55
56
|
easing;
|
|
56
57
|
factor;
|
|
57
58
|
maxSpeed;
|
|
59
|
+
restore;
|
|
58
60
|
speed;
|
|
59
61
|
constructor() {
|
|
60
62
|
this.distance = 200;
|
|
@@ -63,6 +65,12 @@
|
|
|
63
65
|
this.factor = 1;
|
|
64
66
|
this.maxSpeed = 50;
|
|
65
67
|
this.speed = 1;
|
|
68
|
+
this.restore = {
|
|
69
|
+
enable: false,
|
|
70
|
+
delay: 0,
|
|
71
|
+
speed: 0.08,
|
|
72
|
+
follow: true,
|
|
73
|
+
};
|
|
66
74
|
}
|
|
67
75
|
load(data) {
|
|
68
76
|
if (engine.isNull(data)) {
|
|
@@ -86,18 +94,28 @@
|
|
|
86
94
|
if (data.speed !== undefined) {
|
|
87
95
|
this.speed = data.speed;
|
|
88
96
|
}
|
|
97
|
+
if (data.restore !== undefined) {
|
|
98
|
+
this.restore.enable = data.restore.enable ?? this.restore.enable;
|
|
99
|
+
this.restore.delay = data.restore.delay ?? this.restore.delay;
|
|
100
|
+
this.restore.speed = data.restore.speed ?? this.restore.speed;
|
|
101
|
+
this.restore.follow = data.restore.follow ?? this.restore.follow;
|
|
102
|
+
}
|
|
89
103
|
}
|
|
90
104
|
}
|
|
91
105
|
|
|
92
|
-
const attractMode = "attract";
|
|
106
|
+
const attractMode = "attract", minVelocityLengthSq = 0, minRestoreSpeed = 0.001, maxRestoreSpeed = 1, restoreEpsilon = 0.5;
|
|
93
107
|
class Attractor extends pluginInteractivity.ExternalInteractorBase {
|
|
94
108
|
handleClickMode;
|
|
109
|
+
_interactedThisFrame;
|
|
95
110
|
_maxDistance;
|
|
96
111
|
_pluginManager;
|
|
112
|
+
_restoreData;
|
|
97
113
|
constructor(pluginManager, container) {
|
|
98
114
|
super(container);
|
|
99
115
|
this._pluginManager = pluginManager;
|
|
100
116
|
this._maxDistance = 0;
|
|
117
|
+
this._interactedThisFrame = new Set();
|
|
118
|
+
this._restoreData = new Map();
|
|
101
119
|
container.attract ??= { particles: [] };
|
|
102
120
|
this.handleClickMode = (mode, interactivityData) => {
|
|
103
121
|
const options = this.container.actualOptions, attract = options.interactivity?.modes.attract;
|
|
@@ -138,17 +156,23 @@
|
|
|
138
156
|
container.retina.attractModeDistance = attract.distance * container.retina.pixelRatio;
|
|
139
157
|
}
|
|
140
158
|
interact(interactivityData) {
|
|
159
|
+
this._interactedThisFrame.clear();
|
|
141
160
|
const container = this.container, options = container.actualOptions, mouseMoveStatus = interactivityData.status === pluginInteractivity.mouseMoveEvent, events = options.interactivity?.events;
|
|
142
161
|
if (!events) {
|
|
143
162
|
return;
|
|
144
163
|
}
|
|
145
164
|
const { enable: hoverEnabled, mode: hoverMode } = events.onHover, { enable: clickEnabled, mode: clickMode } = events.onClick;
|
|
146
165
|
if (mouseMoveStatus && hoverEnabled && engine.isInArray(attractMode, hoverMode)) {
|
|
147
|
-
hoverAttract(this._pluginManager, this.container, interactivityData, p => this.isEnabled(interactivityData, p)
|
|
166
|
+
hoverAttract(this._pluginManager, this.container, interactivityData, p => this.isEnabled(interactivityData, p), p => {
|
|
167
|
+
this._trackInteractedParticle(p);
|
|
168
|
+
});
|
|
148
169
|
}
|
|
149
170
|
else if (clickEnabled && engine.isInArray(attractMode, clickMode)) {
|
|
150
|
-
clickAttract(this._pluginManager, this.container, interactivityData, p => this.isEnabled(interactivityData, p)
|
|
171
|
+
clickAttract(this._pluginManager, this.container, interactivityData, p => this.isEnabled(interactivityData, p), p => {
|
|
172
|
+
this._trackInteractedParticle(p);
|
|
173
|
+
});
|
|
151
174
|
}
|
|
175
|
+
this._restoreParticles();
|
|
152
176
|
}
|
|
153
177
|
isEnabled(interactivityData, particle) {
|
|
154
178
|
const container = this.container, options = container.actualOptions, mouse = interactivityData.mouse, events = (particle?.interactivity ?? options.interactivity)?.events;
|
|
@@ -166,10 +190,67 @@
|
|
|
166
190
|
}
|
|
167
191
|
reset() {
|
|
168
192
|
}
|
|
193
|
+
_restoreParticles() {
|
|
194
|
+
const restore = this.container.actualOptions.interactivity?.modes.attract?.restore;
|
|
195
|
+
if (!restore?.enable || !this._restoreData.size) {
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
const now = Date.now(), restoreDelay = restore.delay * engine.millisecondsToSeconds, restoreSpeed = Math.max(minRestoreSpeed, Math.min(maxRestoreSpeed, restore.speed));
|
|
199
|
+
for (const [particle, restoreData] of this._restoreData) {
|
|
200
|
+
if (this._interactedThisFrame.has(particle)) {
|
|
201
|
+
continue;
|
|
202
|
+
}
|
|
203
|
+
if (particle.destroyed) {
|
|
204
|
+
this._restoreData.delete(particle);
|
|
205
|
+
continue;
|
|
206
|
+
}
|
|
207
|
+
const target = restoreData.target;
|
|
208
|
+
if (now - restoreData.lastInteractionTime < restoreDelay) {
|
|
209
|
+
continue;
|
|
210
|
+
}
|
|
211
|
+
let dx = target.x - particle.position.x, dy = target.y - particle.position.y, dz = target.z - particle.position.z;
|
|
212
|
+
if (restore.follow && particle.options.move.enable) {
|
|
213
|
+
const { x: vx, y: vy, z: vz } = particle.velocity, velocityLengthSq = vx * vx + vy * vy + vz * vz;
|
|
214
|
+
if (velocityLengthSq > minVelocityLengthSq) {
|
|
215
|
+
const parallelScale = (dx * vx + dy * vy + dz * vz) / velocityLengthSq;
|
|
216
|
+
dx -= vx * parallelScale;
|
|
217
|
+
dy -= vy * parallelScale;
|
|
218
|
+
dz -= vz * parallelScale;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
particle.position.x += dx * restoreSpeed;
|
|
222
|
+
particle.position.y += dy * restoreSpeed;
|
|
223
|
+
particle.position.z += dz * restoreSpeed;
|
|
224
|
+
if (Math.abs(dx) <= restoreEpsilon && Math.abs(dy) <= restoreEpsilon) {
|
|
225
|
+
particle.position.x = target.x;
|
|
226
|
+
particle.position.y = target.y;
|
|
227
|
+
particle.position.z = target.z;
|
|
228
|
+
this._restoreData.delete(particle);
|
|
229
|
+
continue;
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
_trackInteractedParticle(particle) {
|
|
234
|
+
this._interactedThisFrame.add(particle);
|
|
235
|
+
const restore = this.container.actualOptions.interactivity?.modes.attract?.restore;
|
|
236
|
+
if (!restore?.enable) {
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
const now = Date.now();
|
|
240
|
+
let restoreData = this._restoreData.get(particle);
|
|
241
|
+
if (!restoreData) {
|
|
242
|
+
restoreData = {
|
|
243
|
+
target: particle.position.copy(),
|
|
244
|
+
lastInteractionTime: now,
|
|
245
|
+
};
|
|
246
|
+
this._restoreData.set(particle, restoreData);
|
|
247
|
+
}
|
|
248
|
+
restoreData.lastInteractionTime = now;
|
|
249
|
+
}
|
|
169
250
|
}
|
|
170
251
|
|
|
171
252
|
async function loadExternalAttractInteraction(engine) {
|
|
172
|
-
engine.checkVersion("4.0.0-beta.
|
|
253
|
+
engine.checkVersion("4.0.0-beta.17");
|
|
173
254
|
await engine.pluginManager.register((e) => {
|
|
174
255
|
pluginInteractivity.ensureInteractivityPluginLoaded(e);
|
|
175
256
|
e.pluginManager.addInteractor?.("externalAttract", container => {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
!function(t){t.__tsParticlesInternals=t.__tsParticlesInternals||{},t.__tsParticlesInternals.bundles=t.__tsParticlesInternals.bundles||{},t.__tsParticlesInternals.effects=t.__tsParticlesInternals.effects||{},t.__tsParticlesInternals.engine=t.__tsParticlesInternals.engine||{},t.__tsParticlesInternals.interactions=t.__tsParticlesInternals.interactions||{},t.__tsParticlesInternals.palettes=t.__tsParticlesInternals.palettes||{},t.__tsParticlesInternals.paths=t.__tsParticlesInternals.paths||{},t.__tsParticlesInternals.plugins=t.__tsParticlesInternals.plugins||{},t.__tsParticlesInternals.plugins=t.__tsParticlesInternals.plugins||{},t.__tsParticlesInternals.plugins.emittersShapes=t.__tsParticlesInternals.plugins.emittersShapes||{},t.__tsParticlesInternals.presets=t.__tsParticlesInternals.presets||{},t.__tsParticlesInternals.shapes=t.__tsParticlesInternals.shapes||{},t.__tsParticlesInternals.updaters=t.__tsParticlesInternals.updaters||{},t.__tsParticlesInternals.utils=t.__tsParticlesInternals.utils||{},t.__tsParticlesInternals.canvas=t.__tsParticlesInternals.canvas||{},t.__tsParticlesInternals.canvas=t.__tsParticlesInternals.canvas||{},t.__tsParticlesInternals.canvas.utils=t.__tsParticlesInternals.canvas.utils||{},t.__tsParticlesInternals.path=t.__tsParticlesInternals.path||{},t.__tsParticlesInternals.path=t.__tsParticlesInternals.path||{},t.__tsParticlesInternals.path.utils=t.__tsParticlesInternals.path.utils||{};var
|
|
1
|
+
!function(t){t.__tsParticlesInternals=t.__tsParticlesInternals||{},t.__tsParticlesInternals.bundles=t.__tsParticlesInternals.bundles||{},t.__tsParticlesInternals.effects=t.__tsParticlesInternals.effects||{},t.__tsParticlesInternals.engine=t.__tsParticlesInternals.engine||{},t.__tsParticlesInternals.interactions=t.__tsParticlesInternals.interactions||{},t.__tsParticlesInternals.palettes=t.__tsParticlesInternals.palettes||{},t.__tsParticlesInternals.paths=t.__tsParticlesInternals.paths||{},t.__tsParticlesInternals.plugins=t.__tsParticlesInternals.plugins||{},t.__tsParticlesInternals.plugins=t.__tsParticlesInternals.plugins||{},t.__tsParticlesInternals.plugins.emittersShapes=t.__tsParticlesInternals.plugins.emittersShapes||{},t.__tsParticlesInternals.presets=t.__tsParticlesInternals.presets||{},t.__tsParticlesInternals.shapes=t.__tsParticlesInternals.shapes||{},t.__tsParticlesInternals.updaters=t.__tsParticlesInternals.updaters||{},t.__tsParticlesInternals.utils=t.__tsParticlesInternals.utils||{},t.__tsParticlesInternals.canvas=t.__tsParticlesInternals.canvas||{},t.__tsParticlesInternals.canvas=t.__tsParticlesInternals.canvas||{},t.__tsParticlesInternals.canvas.utils=t.__tsParticlesInternals.canvas.utils||{},t.__tsParticlesInternals.path=t.__tsParticlesInternals.path||{},t.__tsParticlesInternals.path=t.__tsParticlesInternals.path||{},t.__tsParticlesInternals.path.utils=t.__tsParticlesInternals.path.utils||{};var e="undefined"!=typeof Proxy?function(t){return new Proxy(t,{get:function(t,e){return e in t||(t[e]={}),t[e]}})}:function(t){return t};t.__tsParticlesInternals.bundles=e(t.__tsParticlesInternals.bundles),t.__tsParticlesInternals.effects=e(t.__tsParticlesInternals.effects),t.__tsParticlesInternals.interactions=e(t.__tsParticlesInternals.interactions),t.__tsParticlesInternals.palettes=e(t.__tsParticlesInternals.palettes),t.__tsParticlesInternals.paths=e(t.__tsParticlesInternals.paths),t.__tsParticlesInternals.plugins=e(t.__tsParticlesInternals.plugins),t.__tsParticlesInternals.plugins.emittersShapes=e(t.__tsParticlesInternals.plugins.emittersShapes),t.__tsParticlesInternals.presets=e(t.__tsParticlesInternals.presets),t.__tsParticlesInternals.shapes=e(t.__tsParticlesInternals.shapes),t.__tsParticlesInternals.updaters=e(t.__tsParticlesInternals.updaters),t.__tsParticlesInternals.utils=e(t.__tsParticlesInternals.utils),t.__tsParticlesInternals.canvas=e(t.__tsParticlesInternals.canvas),t.__tsParticlesInternals.path=e(t.__tsParticlesInternals.path),t.tsparticlesInternalExports=t.tsparticlesInternalExports||{}}("undefined"!=typeof globalThis?globalThis:"undefined"!=typeof window?window:this),function(t,e){"object"==typeof exports&&"undefined"!=typeof module?e(exports,require("@tsparticles/plugin-interactivity"),require("@tsparticles/engine")):"function"==typeof define&&define.amd?define(["exports","@tsparticles/plugin-interactivity","@tsparticles/engine"],e):e(((t="undefined"!=typeof globalThis?globalThis:t||self).__tsParticlesInternals=t.__tsParticlesInternals||{},t.__tsParticlesInternals.interactions=t.__tsParticlesInternals.interactions||{},t.__tsParticlesInternals.interactions.externalAttract=t.__tsParticlesInternals.interactions.externalAttract||{}),t.__tsParticlesInternals.plugins.interactivity,t.__tsParticlesInternals.engine)}(this,function(t,e,s){"use strict";const a=s.Vector.origin;function n(t,e,n,i,r,l,c){const o=e.actualOptions.interactivity?.modes.attract;if(!o)return;const _=e.particles.grid.query(r,l);for(const e of _){const{dx:r,dy:l,distance:_}=s.getDistances(e.position,n),d=o.speed*o.factor,p=s.clamp(t.getEasing(o.easing)(s.identity-_/i)*d,1,o.maxSpeed);a.x=_?r/_*p:d,a.y=_?l/_*p:d,c?.(e),e.position.subFrom(a)}}class i{distance;duration;easing;factor;maxSpeed;restore;speed;constructor(){this.distance=200,this.duration=.4,this.easing=s.EasingType.easeOutQuad,this.factor=1,this.maxSpeed=50,this.speed=1,this.restore={enable:!1,delay:0,speed:.08,follow:!0}}load(t){s.isNull(t)||(void 0!==t.distance&&(this.distance=t.distance),void 0!==t.duration&&(this.duration=t.duration),void 0!==t.easing&&(this.easing=t.easing),void 0!==t.factor&&(this.factor=t.factor),void 0!==t.maxSpeed&&(this.maxSpeed=t.maxSpeed),void 0!==t.speed&&(this.speed=t.speed),void 0!==t.restore&&(this.restore.enable=t.restore.enable??this.restore.enable,this.restore.delay=t.restore.delay??this.restore.delay,this.restore.speed=t.restore.speed??this.restore.speed,this.restore.follow=t.restore.follow??this.restore.follow))}}const r="attract";class l extends e.ExternalInteractorBase{handleClickMode;_interactedThisFrame;_maxDistance;_pluginManager;_restoreData;constructor(t,e){super(e),this._pluginManager=t,this._maxDistance=0,this._interactedThisFrame=new Set,this._restoreData=new Map,e.attract??={particles:[]},this.handleClickMode=(t,a)=>{const n=this.container.actualOptions,i=n.interactivity?.modes.attract;if(i&&t===r){e.attract??={particles:[]},e.attract.clicking=!0,e.attract.count=0;for(const t of e.attract.particles)this.isEnabled(a,t)&&t.velocity.setTo(t.initialVelocity);e.attract.particles=[],e.attract.finish=!1,setTimeout(()=>{e.destroyed||(e.attract??={particles:[]},e.attract.clicking=!1)},i.duration*s.millisecondsToSeconds)}}}get maxDistance(){return this._maxDistance}clear(){}init(){const t=this.container,e=t.actualOptions.interactivity?.modes.attract;e&&(this._maxDistance=e.distance,t.retina.attractModeDistance=e.distance*t.retina.pixelRatio)}interact(t){this._interactedThisFrame.clear();const a=this.container.actualOptions,i=t.status===e.mouseMoveEvent,l=a.interactivity?.events;if(!l)return;const{enable:c,mode:o}=l.onHover,{enable:_,mode:d}=l.onClick;i&&c&&s.isInArray(r,o)?function(t,e,a,i,r){const l=a.mouse.position,c=e.retina.attractModeDistance;!c||c<0||!l||n(t,e,l,c,new s.Circle(l.x,l.y,c),t=>i(t),r)}(this._pluginManager,this.container,t,e=>this.isEnabled(t,e),t=>{this._trackInteractedParticle(t)}):_&&s.isInArray(r,d)&&function(t,e,a,i,r){e.attract??={particles:[]};const{attract:l}=e;if(l.finish||(l.count??=0,l.count++,l.count===e.particles.count&&(l.finish=!0)),l.clicking){const l=a.mouse.clickPosition,c=e.retina.attractModeDistance;if(!c||c<0||!l)return;n(t,e,l,c,new s.Circle(l.x,l.y,c),t=>i(t),r)}else!1===l.clicking&&(l.particles=[])}(this._pluginManager,this.container,t,e=>this.isEnabled(t,e),t=>{this._trackInteractedParticle(t)}),this._restoreParticles()}isEnabled(t,e){const a=this.container.actualOptions,n=t.mouse,i=(e?.interactivity??a.interactivity)?.events;if(!(n.position&&i?.onHover.enable||n.clickPosition&&i?.onClick.enable))return!1;const l=i.onHover.mode,c=i.onClick.mode;return s.isInArray(r,l)||s.isInArray(r,c)}loadModeOptions(t,...e){t.attract??=new i;for(const s of e)t.attract.load(s?.attract)}reset(){}_restoreParticles(){const t=this.container.actualOptions.interactivity?.modes.attract?.restore;if(!t?.enable||!this._restoreData.size)return;const e=Date.now(),a=t.delay*s.millisecondsToSeconds,n=Math.max(.001,Math.min(1,t.speed));for(const[s,i]of this._restoreData){if(this._interactedThisFrame.has(s))continue;if(s.destroyed){this._restoreData.delete(s);continue}const r=i.target;if(e-i.lastInteractionTime<a)continue;let l=r.x-s.position.x,c=r.y-s.position.y,o=r.z-s.position.z;if(t.follow&&s.options.move.enable){const{x:t,y:e,z:a}=s.velocity,n=t*t+e*e+a*a;if(n>0){const s=(l*t+c*e+o*a)/n;l-=t*s,c-=e*s,o-=a*s}}s.position.x+=l*n,s.position.y+=c*n,s.position.z+=o*n,Math.abs(l)<=.5&&Math.abs(c)<=.5&&(s.position.x=r.x,s.position.y=r.y,s.position.z=r.z,this._restoreData.delete(s))}}_trackInteractedParticle(t){this._interactedThisFrame.add(t);const e=this.container.actualOptions.interactivity?.modes.attract?.restore;if(!e?.enable)return;const s=Date.now();let a=this._restoreData.get(t);a||(a={target:t.position.copy(),lastInteractionTime:s},this._restoreData.set(t,a)),a.lastInteractionTime=s}}async function c(t){t.checkVersion("4.0.0-beta.17"),await t.pluginManager.register(t=>{e.ensureInteractivityPluginLoaded(t),t.pluginManager.addInteractor?.("externalAttract",e=>Promise.resolve(new l(t.pluginManager,e)))})}const o=globalThis;o.__tsParticlesInternals=o.__tsParticlesInternals??{},o.loadExternalAttractInteraction=c,t.Attract=i,t.loadExternalAttractInteraction=c}),Object.assign(globalThis.window||globalThis,{loadExternalAttractInteraction:(globalThis.__tsParticlesInternals.interactions.externalAttract||{}).loadExternalAttractInteraction}),delete(globalThis.window||globalThis).tsparticlesInternalExports;
|
package/types/Attractor.d.ts
CHANGED
|
@@ -3,8 +3,10 @@ import { ExternalInteractorBase, type IInteractivityData, type IModes, type Inte
|
|
|
3
3
|
import { type PluginManager, type RecursivePartial } from "@tsparticles/engine";
|
|
4
4
|
export declare class Attractor extends ExternalInteractorBase<AttractContainer> {
|
|
5
5
|
handleClickMode: (mode: string, interactivityData: IInteractivityData) => void;
|
|
6
|
+
private readonly _interactedThisFrame;
|
|
6
7
|
private _maxDistance;
|
|
7
8
|
private readonly _pluginManager;
|
|
9
|
+
private readonly _restoreData;
|
|
8
10
|
constructor(pluginManager: PluginManager, container: AttractContainer);
|
|
9
11
|
get maxDistance(): number;
|
|
10
12
|
clear(): void;
|
|
@@ -13,4 +15,6 @@ export declare class Attractor extends ExternalInteractorBase<AttractContainer>
|
|
|
13
15
|
isEnabled(interactivityData: IInteractivityData, particle?: InteractivityParticle): boolean;
|
|
14
16
|
loadModeOptions(options: Modes & AttractMode, ...sources: RecursivePartial<(IModes & IAttractMode) | undefined>[]): void;
|
|
15
17
|
reset(): void;
|
|
18
|
+
private _restoreParticles;
|
|
19
|
+
private _trackInteractedParticle;
|
|
16
20
|
}
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { EasingType, type EasingTypeAlt, type IOptionLoader, type RecursivePartial } from "@tsparticles/engine";
|
|
2
|
-
import type { IAttract } from "../Interfaces/IAttract.js";
|
|
2
|
+
import type { IAttract, IAttractRestore } from "../Interfaces/IAttract.js";
|
|
3
3
|
export declare class Attract implements IAttract, IOptionLoader<IAttract> {
|
|
4
4
|
distance: number;
|
|
5
5
|
duration: number;
|
|
6
6
|
easing: EasingType | EasingTypeAlt;
|
|
7
7
|
factor: number;
|
|
8
8
|
maxSpeed: number;
|
|
9
|
+
restore: IAttractRestore;
|
|
9
10
|
speed: number;
|
|
10
11
|
constructor();
|
|
11
12
|
load(data?: RecursivePartial<IAttract>): void;
|
|
@@ -1,9 +1,16 @@
|
|
|
1
1
|
import type { EasingType, EasingTypeAlt } from "@tsparticles/engine";
|
|
2
|
+
export interface IAttractRestore {
|
|
3
|
+
delay: number;
|
|
4
|
+
enable: boolean;
|
|
5
|
+
follow: boolean;
|
|
6
|
+
speed: number;
|
|
7
|
+
}
|
|
2
8
|
export interface IAttract {
|
|
3
9
|
distance: number;
|
|
4
10
|
duration: number;
|
|
5
11
|
easing: EasingType | EasingTypeAlt;
|
|
6
12
|
factor: number;
|
|
7
13
|
maxSpeed: number;
|
|
14
|
+
restore: IAttractRestore;
|
|
8
15
|
speed: number;
|
|
9
16
|
}
|
package/types/Types.d.ts
CHANGED
|
@@ -8,7 +8,7 @@ export interface IAttractMode {
|
|
|
8
8
|
export interface AttractMode {
|
|
9
9
|
attract?: Attract;
|
|
10
10
|
}
|
|
11
|
-
interface IContainerAttract {
|
|
11
|
+
export interface IContainerAttract {
|
|
12
12
|
clicking?: boolean;
|
|
13
13
|
count?: number;
|
|
14
14
|
finish?: boolean;
|
|
@@ -21,4 +21,3 @@ export type AttractContainer = InteractivityContainer & {
|
|
|
21
21
|
attractModeDistance?: number;
|
|
22
22
|
};
|
|
23
23
|
};
|
|
24
|
-
export {};
|
package/types/Utils.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type Particle, type PluginManager } from "@tsparticles/engine";
|
|
2
2
|
import type { AttractContainer } from "./Types.js";
|
|
3
3
|
import type { IInteractivityData } from "@tsparticles/plugin-interactivity";
|
|
4
|
-
export declare function clickAttract(pluginManager: PluginManager, container: AttractContainer, interactivityData: IInteractivityData, enabledCb: (particle: Particle) => boolean): void;
|
|
5
|
-
export declare function hoverAttract(pluginManager: PluginManager, container: AttractContainer, interactivityData: IInteractivityData, enabledCb: (particle: Particle) => boolean): void;
|
|
4
|
+
export declare function clickAttract(pluginManager: PluginManager, container: AttractContainer, interactivityData: IInteractivityData, enabledCb: (particle: Particle) => boolean, onAttractParticle?: (particle: Particle) => void): void;
|
|
5
|
+
export declare function hoverAttract(pluginManager: PluginManager, container: AttractContainer, interactivityData: IInteractivityData, enabledCb: (particle: Particle) => boolean, onAttractParticle?: (particle: Particle) => void): void;
|