@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 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)) {
@@ -1,5 +1,11 @@
1
1
  import { EasingType, isNull } from "@tsparticles/engine";
2
2
  export class RepulseBase {
3
+ distance;
4
+ duration;
5
+ easing;
6
+ factor;
7
+ maxSpeed;
8
+ speed;
3
9
  constructor() {
4
10
  this.distance = 200;
5
11
  this.duration = 0.4;
@@ -1,6 +1,7 @@
1
1
  import { isNull } from "@tsparticles/engine";
2
2
  import { RepulseBase } from "./RepulseBase.js";
3
3
  export class RepulseDiv extends RepulseBase {
4
+ selectors;
4
5
  constructor() {
5
6
  super();
6
7
  this.selectors = [];
@@ -1,90 +1,16 @@
1
- import { Circle, DivType, ExternalInteractorBase, Rectangle, Vector, clamp, divMode, divModeExecute, getDistances, isDivModeEnabled, isInArray, millisecondsToSeconds, mouseMoveEvent, safeDocument, } from "@tsparticles/engine";
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, half = 0.5;
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.modes.repulse;
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.modes.repulse;
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 = container.interactivity.status === mouseMoveEvent, events = options.interactivity.events, hover = events.onHover, hoverEnabled = hover.enable, hoverMode = hover.mode, click = events.onClick, clickEnabled = click.enable, clickMode = click.mode, divs = events.onDiv;
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 = container.interactivity.mouse, events = (particle?.interactivity ?? options.interactivity).events, divs = events.onDiv, hover = events.onHover, click = events.onClick, divRepulse = isDivModeEnabled(repulseMode, divs);
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.2");
3
- engine.register(e => {
4
- e.addInteractor("externalRepulse", async (container) => {
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)) {
@@ -1,5 +1,11 @@
1
1
  import { EasingType, isNull } from "@tsparticles/engine";
2
2
  export class RepulseBase {
3
+ distance;
4
+ duration;
5
+ easing;
6
+ factor;
7
+ maxSpeed;
8
+ speed;
3
9
  constructor() {
4
10
  this.distance = 200;
5
11
  this.duration = 0.4;
@@ -1,6 +1,7 @@
1
1
  import { isNull } from "@tsparticles/engine";
2
2
  import { RepulseBase } from "./RepulseBase.js";
3
3
  export class RepulseDiv extends RepulseBase {
4
+ selectors;
4
5
  constructor() {
5
6
  super();
6
7
  this.selectors = [];
package/cjs/Repulser.js CHANGED
@@ -1,90 +1,16 @@
1
- import { Circle, DivType, ExternalInteractorBase, Rectangle, Vector, clamp, divMode, divModeExecute, getDistances, isDivModeEnabled, isInArray, millisecondsToSeconds, mouseMoveEvent, safeDocument, } from "@tsparticles/engine";
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, half = 0.5;
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.modes.repulse;
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.modes.repulse;
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 = container.interactivity.status === mouseMoveEvent, events = options.interactivity.events, hover = events.onHover, hoverEnabled = hover.enable, hoverMode = hover.mode, click = events.onClick, clickEnabled = click.enable, clickMode = click.mode, divs = events.onDiv;
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 = container.interactivity.mouse, events = (particle?.interactivity ?? options.interactivity).events, divs = events.onDiv, hover = events.onHover, click = events.onClick, divRepulse = isDivModeEnabled(repulseMode, divs);
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.2");
3
- engine.register(e => {
4
- e.addInteractor("externalRepulse", async (container) => {
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
  });