@tsparticles/interaction-external-repulse 4.0.0-alpha.2 → 4.0.0-alpha.20
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/780.min.js +1 -0
- package/README.md +5 -0
- package/browser/Options/Classes/Repulse.js +1 -0
- package/browser/Options/Classes/RepulseBase.js +6 -0
- package/browser/Options/Classes/RepulseDiv.js +1 -0
- package/browser/Repulser.js +101 -90
- package/browser/index.js +6 -4
- package/cjs/Options/Classes/Repulse.js +1 -0
- package/cjs/Options/Classes/RepulseBase.js +6 -0
- package/cjs/Options/Classes/RepulseDiv.js +1 -0
- package/cjs/Repulser.js +101 -90
- package/cjs/index.js +6 -4
- package/dist_browser_Repulser_js.js +2 -2
- package/esm/Options/Classes/Repulse.js +1 -0
- package/esm/Options/Classes/RepulseBase.js +6 -0
- package/esm/Options/Classes/RepulseDiv.js +1 -0
- package/esm/Repulser.js +101 -90
- package/esm/index.js +6 -4
- package/package.json +3 -2
- package/report.html +3 -3
- package/tsparticles.interaction.external.repulse.js +59 -19
- package/tsparticles.interaction.external.repulse.min.js +2 -2
- package/types/Options/Classes/RepulseOptions.d.ts +3 -3
- package/types/Options/Interfaces/IRepulseDiv.d.ts +1 -1
- package/types/Repulser.d.ts +5 -4
- package/types/Types.d.ts +3 -2
- package/types/index.d.ts +1 -1
- package/umd/Options/Classes/Repulse.js +1 -0
- package/umd/Options/Classes/RepulseBase.js +6 -0
- package/umd/Options/Classes/RepulseDiv.js +1 -0
- package/umd/Repulser.js +103 -92
- package/umd/index.js +7 -5
- package/176.min.js +0 -2
- package/176.min.js.LICENSE.txt +0 -1
- package/tsparticles.interaction.external.repulse.min.js.LICENSE.txt +0 -1
package/780.min.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";(this.webpackChunk_tsparticles_interaction_external_repulse=this.webpackChunk_tsparticles_interaction_external_repulse||[]).push([[780],{780(e,t,i){i.d(t,{Repulser:()=>o});var s=i(303),l=i(702),n=i(498);let r="repulse";class o extends l.ExternalInteractorBase{handleClickMode;_engine;constructor(e,t){super(t),this._engine=e,t.repulse??={particles:[]},this.handleClickMode=(e,i)=>{let l=this.container.actualOptions,n=l.interactivity?.modes.repulse;if(!n||e!==r)return;t.repulse??={particles:[]};let o=t.repulse;for(let e of(o.clicking=!0,o.count=0,t.repulse.particles))this.isEnabled(i,e)&&e.velocity.setTo(e.initialVelocity);o.particles=[],o.finish=!1,setTimeout(()=>{t.destroyed||(o.clicking=!1)},n.duration*s.millisecondsToSeconds)}}clear(){}init(){let e=this.container,t=e.actualOptions.interactivity?.modes.repulse;t&&(e.retina.repulseModeDistance=t.distance*e.retina.pixelRatio)}interact(e){let t=this.container.actualOptions,i=e.status===l.mouseMoveEvent,n=t.interactivity?.events;if(!n)return;let o=n.onHover,c=o.enable,a=o.mode,p=n.onClick,u=p.enable,f=p.mode,d=n.onDiv;i&&c&&(0,s.isInArray)(r,a)?this._hoverRepulse(e):u&&(0,s.isInArray)(r,f)?this._clickRepulse(e):(0,l.divModeExecute)(r,d,(t,i)=>{this._singleSelectorRepulse(e,t,i)})}isEnabled(e,t){let i=this.container.actualOptions,n=e.mouse,o=(t?.interactivity??i.interactivity)?.events;if(!o)return!1;let c=o.onDiv,a=o.onHover,p=o.onClick,u=(0,l.isDivModeEnabled)(r,c);if(!(u||a.enable&&n.position||p.enable&&n.clickPosition))return!1;let f=a.mode,d=p.mode;return(0,s.isInArray)(r,f)||(0,s.isInArray)(r,d)||u}loadModeOptions(e,...t){for(let i of(e.repulse??=new n.Z,t))e.repulse.load(i?.repulse)}reset(){}_clickRepulse=e=>{let t=this.container,i=t.actualOptions.interactivity?.modes.repulse;if(!i)return;let l=t.repulse??{particles:[]};if(l.finish||(l.count??=0,l.count++,l.count===t.particles.count&&(l.finish=!0)),l.clicking){let n=t.retina.repulseModeDistance;if(!n||n<0)return;let r=Math.pow(n/6,3),o=e.mouse.clickPosition;if(void 0===o)return;let c=new s.Circle(o.x,o.y,r);for(let n of t.particles.quadTree.query(c,t=>this.isEnabled(e,t))){let{dx:e,dy:t,distance:c}=(0,s.getDistances)(o,n.position),a=c**2,p=-r*i.speed/a;if(a<=r){l.particles.push(n);let i=s.Vector.create(e,t);i.length=p,n.velocity.setTo(i)}}}else if(!1===l.clicking){for(let e of l.particles)e.velocity.setTo(e.initialVelocity);l.particles=[]}};_hoverRepulse=e=>{let t=this.container,i=e.mouse.position,l=t.retina.repulseModeDistance;l&&!(l<0)&&i&&this._processRepulse(e,i,l,new s.Circle(i.x,i.y,l))};_processRepulse=(e,t,i,l,n)=>{let r=this.container,o=r.particles.quadTree.query(l,t=>this.isEnabled(e,t)),c=r.actualOptions.interactivity?.modes.repulse;if(!c)return;let{easing:a,speed:p,factor:u,maxSpeed:f}=c,d=this._engine.getEasing(a),h=(n?.speed??p)*u;for(let e of o){let{dx:l,dy:n,distance:r}=(0,s.getDistances)(e.position,t),o=(0,s.clamp)(d(1-r/i)*h,0,f),c=s.Vector.create(r?l/r*o:h,r?n/r*o:h);e.position.addTo(c)}};_singleSelectorRepulse=(e,t,i)=>{let n=this.container,r=n.actualOptions.interactivity?.modes.repulse;if(!r)return;let o=(0,s.safeDocument)().querySelectorAll(t);o.length&&o.forEach(t=>{let o=n.retina.pixelRatio,c={x:(t.offsetLeft+t.offsetWidth*s.half)*o,y:(t.offsetTop+t.offsetHeight*s.half)*o},a=t.offsetWidth*s.half*o,p=i.type===l.DivType.circle?new s.Circle(c.x,c.y,a):new s.Rectangle(t.offsetLeft*o,t.offsetTop*o,t.offsetWidth*o,t.offsetHeight*o),u=r.divs,f=(0,l.divMode)(u,t);this._processRepulse(e,c,a,p,f)})}}}}]);
|
package/README.md
CHANGED
|
@@ -28,6 +28,7 @@ Once the scripts are loaded you can set up `tsParticles` and the interaction plu
|
|
|
28
28
|
|
|
29
29
|
```javascript
|
|
30
30
|
(async () => {
|
|
31
|
+
await loadInteractivityPlugin(tsParticles);
|
|
31
32
|
await loadExternalRepulseInteraction(tsParticles);
|
|
32
33
|
|
|
33
34
|
await tsParticles.load({
|
|
@@ -57,9 +58,11 @@ Then you need to import it in the app, like this:
|
|
|
57
58
|
|
|
58
59
|
```javascript
|
|
59
60
|
const { tsParticles } = require("@tsparticles/engine");
|
|
61
|
+
const { loadInteractivityPlugin } = require("@tsparticles/plugin-interactivity");
|
|
60
62
|
const { loadExternalRepulseInteraction } = require("@tsparticles/interaction-external-repulse");
|
|
61
63
|
|
|
62
64
|
(async () => {
|
|
65
|
+
await loadInteractivityPlugin(tsParticles);
|
|
63
66
|
await loadExternalRepulseInteraction(tsParticles);
|
|
64
67
|
})();
|
|
65
68
|
```
|
|
@@ -68,9 +71,11 @@ or
|
|
|
68
71
|
|
|
69
72
|
```javascript
|
|
70
73
|
import { tsParticles } from "@tsparticles/engine";
|
|
74
|
+
import { loadInteractivityPlugin } from "@tsparticles/plugin-interactivity";
|
|
71
75
|
import { loadExternalRepulseInteraction } from "@tsparticles/interaction-external-repulse";
|
|
72
76
|
|
|
73
77
|
(async () => {
|
|
78
|
+
await loadInteractivityPlugin(tsParticles);
|
|
74
79
|
await loadExternalRepulseInteraction(tsParticles);
|
|
75
80
|
})();
|
|
76
81
|
```
|
|
@@ -2,6 +2,7 @@ import { executeOnSingleOrMultiple, isNull, } from "@tsparticles/engine";
|
|
|
2
2
|
import { RepulseBase } from "./RepulseBase.js";
|
|
3
3
|
import { RepulseDiv } from "./RepulseDiv.js";
|
|
4
4
|
export class Repulse extends RepulseBase {
|
|
5
|
+
divs;
|
|
5
6
|
load(data) {
|
|
6
7
|
super.load(data);
|
|
7
8
|
if (isNull(data)) {
|
package/browser/Repulser.js
CHANGED
|
@@ -1,90 +1,16 @@
|
|
|
1
|
-
import { Circle,
|
|
1
|
+
import { Circle, Rectangle, Vector, clamp, getDistances, half, isInArray, millisecondsToSeconds, safeDocument, } from "@tsparticles/engine";
|
|
2
|
+
import { DivType, ExternalInteractorBase, divMode, divModeExecute, isDivModeEnabled, mouseMoveEvent, } from "@tsparticles/plugin-interactivity";
|
|
2
3
|
import { Repulse } from "./Options/Classes/Repulse.js";
|
|
3
|
-
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;
|
|
4
5
|
export class Repulser extends ExternalInteractorBase {
|
|
6
|
+
handleClickMode;
|
|
7
|
+
_engine;
|
|
5
8
|
constructor(engine, container) {
|
|
6
9
|
super(container);
|
|
7
|
-
this._clickRepulse = () => {
|
|
8
|
-
const container = this.container, repulseOptions = container.actualOptions.interactivity.modes.repulse;
|
|
9
|
-
if (!repulseOptions) {
|
|
10
|
-
return;
|
|
11
|
-
}
|
|
12
|
-
const repulse = container.repulse ?? { particles: [] };
|
|
13
|
-
if (!repulse.finish) {
|
|
14
|
-
repulse.count ??= 0;
|
|
15
|
-
repulse.count++;
|
|
16
|
-
if (repulse.count === container.particles.count) {
|
|
17
|
-
repulse.finish = true;
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
|
-
if (repulse.clicking) {
|
|
21
|
-
const repulseDistance = container.retina.repulseModeDistance;
|
|
22
|
-
if (!repulseDistance || repulseDistance < minDistance) {
|
|
23
|
-
return;
|
|
24
|
-
}
|
|
25
|
-
const repulseRadius = Math.pow(repulseDistance / repulseRadiusFactor, repulseRadiusPower), mouseClickPos = container.interactivity.mouse.clickPosition;
|
|
26
|
-
if (mouseClickPos === undefined) {
|
|
27
|
-
return;
|
|
28
|
-
}
|
|
29
|
-
const range = new Circle(mouseClickPos.x, mouseClickPos.y, repulseRadius), query = container.particles.quadTree.query(range, p => this.isEnabled(p));
|
|
30
|
-
for (const particle of query) {
|
|
31
|
-
const { dx, dy, distance } = getDistances(mouseClickPos, particle.position), d = distance ** squarePower, velocity = repulseOptions.speed, force = (-repulseRadius * velocity) / d;
|
|
32
|
-
if (d <= repulseRadius) {
|
|
33
|
-
repulse.particles.push(particle);
|
|
34
|
-
const vect = Vector.create(dx, dy);
|
|
35
|
-
vect.length = force;
|
|
36
|
-
particle.velocity.setTo(vect);
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
else if (repulse.clicking === false) {
|
|
41
|
-
for (const particle of repulse.particles) {
|
|
42
|
-
particle.velocity.setTo(particle.initialVelocity);
|
|
43
|
-
}
|
|
44
|
-
repulse.particles = [];
|
|
45
|
-
}
|
|
46
|
-
};
|
|
47
|
-
this._hoverRepulse = () => {
|
|
48
|
-
const container = this.container, mousePos = container.interactivity.mouse.position, repulseRadius = container.retina.repulseModeDistance;
|
|
49
|
-
if (!repulseRadius || repulseRadius < minRadius || !mousePos) {
|
|
50
|
-
return;
|
|
51
|
-
}
|
|
52
|
-
this._processRepulse(mousePos, repulseRadius, new Circle(mousePos.x, mousePos.y, repulseRadius));
|
|
53
|
-
};
|
|
54
|
-
this._processRepulse = (position, repulseRadius, area, divRepulse) => {
|
|
55
|
-
const container = this.container, query = container.particles.quadTree.query(area, p => this.isEnabled(p)), repulseOptions = container.actualOptions.interactivity.modes.repulse;
|
|
56
|
-
if (!repulseOptions) {
|
|
57
|
-
return;
|
|
58
|
-
}
|
|
59
|
-
const { easing, speed, factor, maxSpeed } = repulseOptions, easingFunc = this._engine.getEasing(easing), velocity = (divRepulse?.speed ?? speed) * factor;
|
|
60
|
-
for (const particle of query) {
|
|
61
|
-
const { dx, dy, distance } = getDistances(particle.position, position), repulseFactor = clamp(easingFunc(easingOffset - distance / repulseRadius) * velocity, minSpeed, maxSpeed), normVec = Vector.create(!distance ? velocity : (dx / distance) * repulseFactor, !distance ? velocity : (dy / distance) * repulseFactor);
|
|
62
|
-
particle.position.addTo(normVec);
|
|
63
|
-
}
|
|
64
|
-
};
|
|
65
|
-
this._singleSelectorRepulse = (selector, div) => {
|
|
66
|
-
const container = this.container, repulse = container.actualOptions.interactivity.modes.repulse;
|
|
67
|
-
if (!repulse) {
|
|
68
|
-
return;
|
|
69
|
-
}
|
|
70
|
-
const query = safeDocument().querySelectorAll(selector);
|
|
71
|
-
if (!query.length) {
|
|
72
|
-
return;
|
|
73
|
-
}
|
|
74
|
-
query.forEach(item => {
|
|
75
|
-
const elem = item, pxRatio = container.retina.pixelRatio, pos = {
|
|
76
|
-
x: (elem.offsetLeft + elem.offsetWidth * half) * pxRatio,
|
|
77
|
-
y: (elem.offsetTop + elem.offsetHeight * half) * pxRatio,
|
|
78
|
-
}, repulseRadius = elem.offsetWidth * half * pxRatio, area = div.type === DivType.circle
|
|
79
|
-
? new Circle(pos.x, pos.y, repulseRadius)
|
|
80
|
-
: new Rectangle(elem.offsetLeft * pxRatio, elem.offsetTop * pxRatio, elem.offsetWidth * pxRatio, elem.offsetHeight * pxRatio), divs = repulse.divs, divRepulse = divMode(divs, elem);
|
|
81
|
-
this._processRepulse(pos, repulseRadius, area, divRepulse);
|
|
82
|
-
});
|
|
83
|
-
};
|
|
84
10
|
this._engine = engine;
|
|
85
11
|
container.repulse ??= { particles: [] };
|
|
86
|
-
this.handleClickMode = (mode) => {
|
|
87
|
-
const options = this.container.actualOptions, repulseOpts = options.interactivity
|
|
12
|
+
this.handleClickMode = (mode, interactivityData) => {
|
|
13
|
+
const options = this.container.actualOptions, repulseOpts = options.interactivity?.modes.repulse;
|
|
88
14
|
if (!repulseOpts || mode !== repulseMode) {
|
|
89
15
|
return;
|
|
90
16
|
}
|
|
@@ -93,7 +19,7 @@ export class Repulser extends ExternalInteractorBase {
|
|
|
93
19
|
repulse.clicking = true;
|
|
94
20
|
repulse.count = 0;
|
|
95
21
|
for (const particle of container.repulse.particles) {
|
|
96
|
-
if (!this.isEnabled(particle)) {
|
|
22
|
+
if (!this.isEnabled(interactivityData, particle)) {
|
|
97
23
|
continue;
|
|
98
24
|
}
|
|
99
25
|
particle.velocity.setTo(particle.initialVelocity);
|
|
@@ -111,28 +37,36 @@ export class Repulser extends ExternalInteractorBase {
|
|
|
111
37
|
clear() {
|
|
112
38
|
}
|
|
113
39
|
init() {
|
|
114
|
-
const container = this.container, repulse = container.actualOptions.interactivity
|
|
40
|
+
const container = this.container, repulse = container.actualOptions.interactivity?.modes.repulse;
|
|
115
41
|
if (!repulse) {
|
|
116
42
|
return;
|
|
117
43
|
}
|
|
118
44
|
container.retina.repulseModeDistance = repulse.distance * container.retina.pixelRatio;
|
|
119
45
|
}
|
|
120
|
-
interact() {
|
|
121
|
-
const container = this.container, options = container.actualOptions, mouseMoveStatus =
|
|
46
|
+
interact(interactivityData) {
|
|
47
|
+
const container = this.container, options = container.actualOptions, mouseMoveStatus = interactivityData.status === mouseMoveEvent, events = options.interactivity?.events;
|
|
48
|
+
if (!events) {
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
const hover = events.onHover, hoverEnabled = hover.enable, hoverMode = hover.mode, click = events.onClick, clickEnabled = click.enable, clickMode = click.mode, divs = events.onDiv;
|
|
122
52
|
if (mouseMoveStatus && hoverEnabled && isInArray(repulseMode, hoverMode)) {
|
|
123
|
-
this._hoverRepulse();
|
|
53
|
+
this._hoverRepulse(interactivityData);
|
|
124
54
|
}
|
|
125
55
|
else if (clickEnabled && isInArray(repulseMode, clickMode)) {
|
|
126
|
-
this._clickRepulse();
|
|
56
|
+
this._clickRepulse(interactivityData);
|
|
127
57
|
}
|
|
128
58
|
else {
|
|
129
59
|
divModeExecute(repulseMode, divs, (selector, div) => {
|
|
130
|
-
this._singleSelectorRepulse(selector, div);
|
|
60
|
+
this._singleSelectorRepulse(interactivityData, selector, div);
|
|
131
61
|
});
|
|
132
62
|
}
|
|
133
63
|
}
|
|
134
|
-
isEnabled(particle) {
|
|
135
|
-
const container = this.container, options = container.actualOptions, mouse =
|
|
64
|
+
isEnabled(interactivityData, particle) {
|
|
65
|
+
const container = this.container, options = container.actualOptions, mouse = interactivityData.mouse, events = (particle?.interactivity ?? options.interactivity)?.events;
|
|
66
|
+
if (!events) {
|
|
67
|
+
return false;
|
|
68
|
+
}
|
|
69
|
+
const divs = events.onDiv, hover = events.onHover, click = events.onClick, divRepulse = isDivModeEnabled(repulseMode, divs);
|
|
136
70
|
if (!(divRepulse || (hover.enable && !!mouse.position) || (click.enable && mouse.clickPosition))) {
|
|
137
71
|
return false;
|
|
138
72
|
}
|
|
@@ -147,4 +81,81 @@ export class Repulser extends ExternalInteractorBase {
|
|
|
147
81
|
}
|
|
148
82
|
reset() {
|
|
149
83
|
}
|
|
84
|
+
_clickRepulse = interactivityData => {
|
|
85
|
+
const container = this.container, repulseOptions = container.actualOptions.interactivity?.modes.repulse;
|
|
86
|
+
if (!repulseOptions) {
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
const repulse = container.repulse ?? { particles: [] };
|
|
90
|
+
if (!repulse.finish) {
|
|
91
|
+
repulse.count ??= 0;
|
|
92
|
+
repulse.count++;
|
|
93
|
+
if (repulse.count === container.particles.count) {
|
|
94
|
+
repulse.finish = true;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
if (repulse.clicking) {
|
|
98
|
+
const repulseDistance = container.retina.repulseModeDistance;
|
|
99
|
+
if (!repulseDistance || repulseDistance < minDistance) {
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
const repulseRadius = Math.pow(repulseDistance / repulseRadiusFactor, repulseRadiusPower), mouseClickPos = interactivityData.mouse.clickPosition;
|
|
103
|
+
if (mouseClickPos === undefined) {
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
const range = new Circle(mouseClickPos.x, mouseClickPos.y, repulseRadius), query = container.particles.quadTree.query(range, p => this.isEnabled(interactivityData, p));
|
|
107
|
+
for (const particle of query) {
|
|
108
|
+
const { dx, dy, distance } = getDistances(mouseClickPos, particle.position), d = distance ** squarePower, velocity = repulseOptions.speed, force = (-repulseRadius * velocity) / d;
|
|
109
|
+
if (d <= repulseRadius) {
|
|
110
|
+
repulse.particles.push(particle);
|
|
111
|
+
const vect = Vector.create(dx, dy);
|
|
112
|
+
vect.length = force;
|
|
113
|
+
particle.velocity.setTo(vect);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
else if (repulse.clicking === false) {
|
|
118
|
+
for (const particle of repulse.particles) {
|
|
119
|
+
particle.velocity.setTo(particle.initialVelocity);
|
|
120
|
+
}
|
|
121
|
+
repulse.particles = [];
|
|
122
|
+
}
|
|
123
|
+
};
|
|
124
|
+
_hoverRepulse = interactivityData => {
|
|
125
|
+
const container = this.container, mousePos = interactivityData.mouse.position, repulseRadius = container.retina.repulseModeDistance;
|
|
126
|
+
if (!repulseRadius || repulseRadius < minRadius || !mousePos) {
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
this._processRepulse(interactivityData, mousePos, repulseRadius, new Circle(mousePos.x, mousePos.y, repulseRadius));
|
|
130
|
+
};
|
|
131
|
+
_processRepulse = (interactivityData, position, repulseRadius, area, divRepulse) => {
|
|
132
|
+
const container = this.container, query = container.particles.quadTree.query(area, p => this.isEnabled(interactivityData, p)), repulseOptions = container.actualOptions.interactivity?.modes.repulse;
|
|
133
|
+
if (!repulseOptions) {
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
const { easing, speed, factor, maxSpeed } = repulseOptions, easingFunc = this._engine.getEasing(easing), velocity = (divRepulse?.speed ?? speed) * factor;
|
|
137
|
+
for (const particle of query) {
|
|
138
|
+
const { dx, dy, distance } = getDistances(particle.position, position), repulseFactor = clamp(easingFunc(easingOffset - distance / repulseRadius) * velocity, minSpeed, maxSpeed), normVec = Vector.create(!distance ? velocity : (dx / distance) * repulseFactor, !distance ? velocity : (dy / distance) * repulseFactor);
|
|
139
|
+
particle.position.addTo(normVec);
|
|
140
|
+
}
|
|
141
|
+
};
|
|
142
|
+
_singleSelectorRepulse = (interactivityData, selector, div) => {
|
|
143
|
+
const container = this.container, repulse = container.actualOptions.interactivity?.modes.repulse;
|
|
144
|
+
if (!repulse) {
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
const query = safeDocument().querySelectorAll(selector);
|
|
148
|
+
if (!query.length) {
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
query.forEach(item => {
|
|
152
|
+
const elem = item, pxRatio = container.retina.pixelRatio, pos = {
|
|
153
|
+
x: (elem.offsetLeft + elem.offsetWidth * half) * pxRatio,
|
|
154
|
+
y: (elem.offsetTop + elem.offsetHeight * half) * pxRatio,
|
|
155
|
+
}, repulseRadius = elem.offsetWidth * half * pxRatio, area = div.type === DivType.circle
|
|
156
|
+
? new Circle(pos.x, pos.y, repulseRadius)
|
|
157
|
+
: new Rectangle(elem.offsetLeft * pxRatio, elem.offsetTop * pxRatio, elem.offsetWidth * pxRatio, elem.offsetHeight * pxRatio), divs = repulse.divs, divRepulse = divMode(divs, elem);
|
|
158
|
+
this._processRepulse(interactivityData, pos, repulseRadius, area, divRepulse);
|
|
159
|
+
});
|
|
160
|
+
};
|
|
150
161
|
}
|
package/browser/index.js
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
|
-
export function loadExternalRepulseInteraction(engine) {
|
|
2
|
-
engine.checkVersion("4.0.0-alpha.
|
|
3
|
-
engine.register(e => {
|
|
4
|
-
|
|
1
|
+
export async function loadExternalRepulseInteraction(engine) {
|
|
2
|
+
engine.checkVersion("4.0.0-alpha.20");
|
|
3
|
+
await engine.register(async (e) => {
|
|
4
|
+
const { ensureInteractivityPluginLoaded } = await import("@tsparticles/plugin-interactivity");
|
|
5
|
+
ensureInteractivityPluginLoaded(e);
|
|
6
|
+
e.addInteractor?.("externalRepulse", async (container) => {
|
|
5
7
|
const { Repulser } = await import("./Repulser.js");
|
|
6
8
|
return new Repulser(engine, container);
|
|
7
9
|
});
|
|
@@ -2,6 +2,7 @@ import { executeOnSingleOrMultiple, isNull, } from "@tsparticles/engine";
|
|
|
2
2
|
import { RepulseBase } from "./RepulseBase.js";
|
|
3
3
|
import { RepulseDiv } from "./RepulseDiv.js";
|
|
4
4
|
export class Repulse extends RepulseBase {
|
|
5
|
+
divs;
|
|
5
6
|
load(data) {
|
|
6
7
|
super.load(data);
|
|
7
8
|
if (isNull(data)) {
|
package/cjs/Repulser.js
CHANGED
|
@@ -1,90 +1,16 @@
|
|
|
1
|
-
import { Circle,
|
|
1
|
+
import { Circle, Rectangle, Vector, clamp, getDistances, half, isInArray, millisecondsToSeconds, safeDocument, } from "@tsparticles/engine";
|
|
2
|
+
import { DivType, ExternalInteractorBase, divMode, divModeExecute, isDivModeEnabled, mouseMoveEvent, } from "@tsparticles/plugin-interactivity";
|
|
2
3
|
import { Repulse } from "./Options/Classes/Repulse.js";
|
|
3
|
-
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;
|
|
4
5
|
export class Repulser extends ExternalInteractorBase {
|
|
6
|
+
handleClickMode;
|
|
7
|
+
_engine;
|
|
5
8
|
constructor(engine, container) {
|
|
6
9
|
super(container);
|
|
7
|
-
this._clickRepulse = () => {
|
|
8
|
-
const container = this.container, repulseOptions = container.actualOptions.interactivity.modes.repulse;
|
|
9
|
-
if (!repulseOptions) {
|
|
10
|
-
return;
|
|
11
|
-
}
|
|
12
|
-
const repulse = container.repulse ?? { particles: [] };
|
|
13
|
-
if (!repulse.finish) {
|
|
14
|
-
repulse.count ??= 0;
|
|
15
|
-
repulse.count++;
|
|
16
|
-
if (repulse.count === container.particles.count) {
|
|
17
|
-
repulse.finish = true;
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
|
-
if (repulse.clicking) {
|
|
21
|
-
const repulseDistance = container.retina.repulseModeDistance;
|
|
22
|
-
if (!repulseDistance || repulseDistance < minDistance) {
|
|
23
|
-
return;
|
|
24
|
-
}
|
|
25
|
-
const repulseRadius = Math.pow(repulseDistance / repulseRadiusFactor, repulseRadiusPower), mouseClickPos = container.interactivity.mouse.clickPosition;
|
|
26
|
-
if (mouseClickPos === undefined) {
|
|
27
|
-
return;
|
|
28
|
-
}
|
|
29
|
-
const range = new Circle(mouseClickPos.x, mouseClickPos.y, repulseRadius), query = container.particles.quadTree.query(range, p => this.isEnabled(p));
|
|
30
|
-
for (const particle of query) {
|
|
31
|
-
const { dx, dy, distance } = getDistances(mouseClickPos, particle.position), d = distance ** squarePower, velocity = repulseOptions.speed, force = (-repulseRadius * velocity) / d;
|
|
32
|
-
if (d <= repulseRadius) {
|
|
33
|
-
repulse.particles.push(particle);
|
|
34
|
-
const vect = Vector.create(dx, dy);
|
|
35
|
-
vect.length = force;
|
|
36
|
-
particle.velocity.setTo(vect);
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
else if (repulse.clicking === false) {
|
|
41
|
-
for (const particle of repulse.particles) {
|
|
42
|
-
particle.velocity.setTo(particle.initialVelocity);
|
|
43
|
-
}
|
|
44
|
-
repulse.particles = [];
|
|
45
|
-
}
|
|
46
|
-
};
|
|
47
|
-
this._hoverRepulse = () => {
|
|
48
|
-
const container = this.container, mousePos = container.interactivity.mouse.position, repulseRadius = container.retina.repulseModeDistance;
|
|
49
|
-
if (!repulseRadius || repulseRadius < minRadius || !mousePos) {
|
|
50
|
-
return;
|
|
51
|
-
}
|
|
52
|
-
this._processRepulse(mousePos, repulseRadius, new Circle(mousePos.x, mousePos.y, repulseRadius));
|
|
53
|
-
};
|
|
54
|
-
this._processRepulse = (position, repulseRadius, area, divRepulse) => {
|
|
55
|
-
const container = this.container, query = container.particles.quadTree.query(area, p => this.isEnabled(p)), repulseOptions = container.actualOptions.interactivity.modes.repulse;
|
|
56
|
-
if (!repulseOptions) {
|
|
57
|
-
return;
|
|
58
|
-
}
|
|
59
|
-
const { easing, speed, factor, maxSpeed } = repulseOptions, easingFunc = this._engine.getEasing(easing), velocity = (divRepulse?.speed ?? speed) * factor;
|
|
60
|
-
for (const particle of query) {
|
|
61
|
-
const { dx, dy, distance } = getDistances(particle.position, position), repulseFactor = clamp(easingFunc(easingOffset - distance / repulseRadius) * velocity, minSpeed, maxSpeed), normVec = Vector.create(!distance ? velocity : (dx / distance) * repulseFactor, !distance ? velocity : (dy / distance) * repulseFactor);
|
|
62
|
-
particle.position.addTo(normVec);
|
|
63
|
-
}
|
|
64
|
-
};
|
|
65
|
-
this._singleSelectorRepulse = (selector, div) => {
|
|
66
|
-
const container = this.container, repulse = container.actualOptions.interactivity.modes.repulse;
|
|
67
|
-
if (!repulse) {
|
|
68
|
-
return;
|
|
69
|
-
}
|
|
70
|
-
const query = safeDocument().querySelectorAll(selector);
|
|
71
|
-
if (!query.length) {
|
|
72
|
-
return;
|
|
73
|
-
}
|
|
74
|
-
query.forEach(item => {
|
|
75
|
-
const elem = item, pxRatio = container.retina.pixelRatio, pos = {
|
|
76
|
-
x: (elem.offsetLeft + elem.offsetWidth * half) * pxRatio,
|
|
77
|
-
y: (elem.offsetTop + elem.offsetHeight * half) * pxRatio,
|
|
78
|
-
}, repulseRadius = elem.offsetWidth * half * pxRatio, area = div.type === DivType.circle
|
|
79
|
-
? new Circle(pos.x, pos.y, repulseRadius)
|
|
80
|
-
: new Rectangle(elem.offsetLeft * pxRatio, elem.offsetTop * pxRatio, elem.offsetWidth * pxRatio, elem.offsetHeight * pxRatio), divs = repulse.divs, divRepulse = divMode(divs, elem);
|
|
81
|
-
this._processRepulse(pos, repulseRadius, area, divRepulse);
|
|
82
|
-
});
|
|
83
|
-
};
|
|
84
10
|
this._engine = engine;
|
|
85
11
|
container.repulse ??= { particles: [] };
|
|
86
|
-
this.handleClickMode = (mode) => {
|
|
87
|
-
const options = this.container.actualOptions, repulseOpts = options.interactivity
|
|
12
|
+
this.handleClickMode = (mode, interactivityData) => {
|
|
13
|
+
const options = this.container.actualOptions, repulseOpts = options.interactivity?.modes.repulse;
|
|
88
14
|
if (!repulseOpts || mode !== repulseMode) {
|
|
89
15
|
return;
|
|
90
16
|
}
|
|
@@ -93,7 +19,7 @@ export class Repulser extends ExternalInteractorBase {
|
|
|
93
19
|
repulse.clicking = true;
|
|
94
20
|
repulse.count = 0;
|
|
95
21
|
for (const particle of container.repulse.particles) {
|
|
96
|
-
if (!this.isEnabled(particle)) {
|
|
22
|
+
if (!this.isEnabled(interactivityData, particle)) {
|
|
97
23
|
continue;
|
|
98
24
|
}
|
|
99
25
|
particle.velocity.setTo(particle.initialVelocity);
|
|
@@ -111,28 +37,36 @@ export class Repulser extends ExternalInteractorBase {
|
|
|
111
37
|
clear() {
|
|
112
38
|
}
|
|
113
39
|
init() {
|
|
114
|
-
const container = this.container, repulse = container.actualOptions.interactivity
|
|
40
|
+
const container = this.container, repulse = container.actualOptions.interactivity?.modes.repulse;
|
|
115
41
|
if (!repulse) {
|
|
116
42
|
return;
|
|
117
43
|
}
|
|
118
44
|
container.retina.repulseModeDistance = repulse.distance * container.retina.pixelRatio;
|
|
119
45
|
}
|
|
120
|
-
interact() {
|
|
121
|
-
const container = this.container, options = container.actualOptions, mouseMoveStatus =
|
|
46
|
+
interact(interactivityData) {
|
|
47
|
+
const container = this.container, options = container.actualOptions, mouseMoveStatus = interactivityData.status === mouseMoveEvent, events = options.interactivity?.events;
|
|
48
|
+
if (!events) {
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
const hover = events.onHover, hoverEnabled = hover.enable, hoverMode = hover.mode, click = events.onClick, clickEnabled = click.enable, clickMode = click.mode, divs = events.onDiv;
|
|
122
52
|
if (mouseMoveStatus && hoverEnabled && isInArray(repulseMode, hoverMode)) {
|
|
123
|
-
this._hoverRepulse();
|
|
53
|
+
this._hoverRepulse(interactivityData);
|
|
124
54
|
}
|
|
125
55
|
else if (clickEnabled && isInArray(repulseMode, clickMode)) {
|
|
126
|
-
this._clickRepulse();
|
|
56
|
+
this._clickRepulse(interactivityData);
|
|
127
57
|
}
|
|
128
58
|
else {
|
|
129
59
|
divModeExecute(repulseMode, divs, (selector, div) => {
|
|
130
|
-
this._singleSelectorRepulse(selector, div);
|
|
60
|
+
this._singleSelectorRepulse(interactivityData, selector, div);
|
|
131
61
|
});
|
|
132
62
|
}
|
|
133
63
|
}
|
|
134
|
-
isEnabled(particle) {
|
|
135
|
-
const container = this.container, options = container.actualOptions, mouse =
|
|
64
|
+
isEnabled(interactivityData, particle) {
|
|
65
|
+
const container = this.container, options = container.actualOptions, mouse = interactivityData.mouse, events = (particle?.interactivity ?? options.interactivity)?.events;
|
|
66
|
+
if (!events) {
|
|
67
|
+
return false;
|
|
68
|
+
}
|
|
69
|
+
const divs = events.onDiv, hover = events.onHover, click = events.onClick, divRepulse = isDivModeEnabled(repulseMode, divs);
|
|
136
70
|
if (!(divRepulse || (hover.enable && !!mouse.position) || (click.enable && mouse.clickPosition))) {
|
|
137
71
|
return false;
|
|
138
72
|
}
|
|
@@ -147,4 +81,81 @@ export class Repulser extends ExternalInteractorBase {
|
|
|
147
81
|
}
|
|
148
82
|
reset() {
|
|
149
83
|
}
|
|
84
|
+
_clickRepulse = interactivityData => {
|
|
85
|
+
const container = this.container, repulseOptions = container.actualOptions.interactivity?.modes.repulse;
|
|
86
|
+
if (!repulseOptions) {
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
const repulse = container.repulse ?? { particles: [] };
|
|
90
|
+
if (!repulse.finish) {
|
|
91
|
+
repulse.count ??= 0;
|
|
92
|
+
repulse.count++;
|
|
93
|
+
if (repulse.count === container.particles.count) {
|
|
94
|
+
repulse.finish = true;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
if (repulse.clicking) {
|
|
98
|
+
const repulseDistance = container.retina.repulseModeDistance;
|
|
99
|
+
if (!repulseDistance || repulseDistance < minDistance) {
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
const repulseRadius = Math.pow(repulseDistance / repulseRadiusFactor, repulseRadiusPower), mouseClickPos = interactivityData.mouse.clickPosition;
|
|
103
|
+
if (mouseClickPos === undefined) {
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
const range = new Circle(mouseClickPos.x, mouseClickPos.y, repulseRadius), query = container.particles.quadTree.query(range, p => this.isEnabled(interactivityData, p));
|
|
107
|
+
for (const particle of query) {
|
|
108
|
+
const { dx, dy, distance } = getDistances(mouseClickPos, particle.position), d = distance ** squarePower, velocity = repulseOptions.speed, force = (-repulseRadius * velocity) / d;
|
|
109
|
+
if (d <= repulseRadius) {
|
|
110
|
+
repulse.particles.push(particle);
|
|
111
|
+
const vect = Vector.create(dx, dy);
|
|
112
|
+
vect.length = force;
|
|
113
|
+
particle.velocity.setTo(vect);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
else if (repulse.clicking === false) {
|
|
118
|
+
for (const particle of repulse.particles) {
|
|
119
|
+
particle.velocity.setTo(particle.initialVelocity);
|
|
120
|
+
}
|
|
121
|
+
repulse.particles = [];
|
|
122
|
+
}
|
|
123
|
+
};
|
|
124
|
+
_hoverRepulse = interactivityData => {
|
|
125
|
+
const container = this.container, mousePos = interactivityData.mouse.position, repulseRadius = container.retina.repulseModeDistance;
|
|
126
|
+
if (!repulseRadius || repulseRadius < minRadius || !mousePos) {
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
this._processRepulse(interactivityData, mousePos, repulseRadius, new Circle(mousePos.x, mousePos.y, repulseRadius));
|
|
130
|
+
};
|
|
131
|
+
_processRepulse = (interactivityData, position, repulseRadius, area, divRepulse) => {
|
|
132
|
+
const container = this.container, query = container.particles.quadTree.query(area, p => this.isEnabled(interactivityData, p)), repulseOptions = container.actualOptions.interactivity?.modes.repulse;
|
|
133
|
+
if (!repulseOptions) {
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
const { easing, speed, factor, maxSpeed } = repulseOptions, easingFunc = this._engine.getEasing(easing), velocity = (divRepulse?.speed ?? speed) * factor;
|
|
137
|
+
for (const particle of query) {
|
|
138
|
+
const { dx, dy, distance } = getDistances(particle.position, position), repulseFactor = clamp(easingFunc(easingOffset - distance / repulseRadius) * velocity, minSpeed, maxSpeed), normVec = Vector.create(!distance ? velocity : (dx / distance) * repulseFactor, !distance ? velocity : (dy / distance) * repulseFactor);
|
|
139
|
+
particle.position.addTo(normVec);
|
|
140
|
+
}
|
|
141
|
+
};
|
|
142
|
+
_singleSelectorRepulse = (interactivityData, selector, div) => {
|
|
143
|
+
const container = this.container, repulse = container.actualOptions.interactivity?.modes.repulse;
|
|
144
|
+
if (!repulse) {
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
const query = safeDocument().querySelectorAll(selector);
|
|
148
|
+
if (!query.length) {
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
query.forEach(item => {
|
|
152
|
+
const elem = item, pxRatio = container.retina.pixelRatio, pos = {
|
|
153
|
+
x: (elem.offsetLeft + elem.offsetWidth * half) * pxRatio,
|
|
154
|
+
y: (elem.offsetTop + elem.offsetHeight * half) * pxRatio,
|
|
155
|
+
}, repulseRadius = elem.offsetWidth * half * pxRatio, area = div.type === DivType.circle
|
|
156
|
+
? new Circle(pos.x, pos.y, repulseRadius)
|
|
157
|
+
: new Rectangle(elem.offsetLeft * pxRatio, elem.offsetTop * pxRatio, elem.offsetWidth * pxRatio, elem.offsetHeight * pxRatio), divs = repulse.divs, divRepulse = divMode(divs, elem);
|
|
158
|
+
this._processRepulse(interactivityData, pos, repulseRadius, area, divRepulse);
|
|
159
|
+
});
|
|
160
|
+
};
|
|
150
161
|
}
|
package/cjs/index.js
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
|
-
export function loadExternalRepulseInteraction(engine) {
|
|
2
|
-
engine.checkVersion("4.0.0-alpha.
|
|
3
|
-
engine.register(e => {
|
|
4
|
-
|
|
1
|
+
export async function loadExternalRepulseInteraction(engine) {
|
|
2
|
+
engine.checkVersion("4.0.0-alpha.20");
|
|
3
|
+
await engine.register(async (e) => {
|
|
4
|
+
const { ensureInteractivityPluginLoaded } = await import("@tsparticles/plugin-interactivity");
|
|
5
|
+
ensureInteractivityPluginLoaded(e);
|
|
6
|
+
e.addInteractor?.("externalRepulse", async (container) => {
|
|
5
7
|
const { Repulser } = await import("./Repulser.js");
|
|
6
8
|
return new Repulser(engine, container);
|
|
7
9
|
});
|