1 | import { ClickMode, DivMode, DivType, HoverMode } from "../../Enums";
|
2 | import { Circle, Constants, NumberUtils, Rectangle, Utils } from "../../Utils";
|
3 | import { Vector } from "../../Core/Particle/Vector";
|
4 | export class Repulser {
|
5 | constructor(container) {
|
6 | this.container = container;
|
7 | }
|
8 | isEnabled() {
|
9 | const container = this.container;
|
10 | const options = container.actualOptions;
|
11 | const mouse = container.interactivity.mouse;
|
12 | const events = options.interactivity.events;
|
13 | const divs = events.onDiv;
|
14 | const divRepulse = Utils.isDivModeEnabled(DivMode.repulse, divs);
|
15 | if (!(divRepulse || (events.onHover.enable && mouse.position) || (events.onClick.enable && mouse.clickPosition))) {
|
16 | return false;
|
17 | }
|
18 | const hoverMode = events.onHover.mode;
|
19 | const clickMode = events.onClick.mode;
|
20 | return (Utils.isInArray(HoverMode.repulse, hoverMode) || Utils.isInArray(ClickMode.repulse, clickMode) || divRepulse);
|
21 | }
|
22 | reset() {
|
23 | }
|
24 | interact() {
|
25 | const container = this.container;
|
26 | const options = container.actualOptions;
|
27 | const mouseMoveStatus = container.interactivity.status === Constants.mouseMoveEvent;
|
28 | const events = options.interactivity.events;
|
29 | const hoverEnabled = events.onHover.enable;
|
30 | const hoverMode = events.onHover.mode;
|
31 | const clickEnabled = events.onClick.enable;
|
32 | const clickMode = events.onClick.mode;
|
33 | const divs = events.onDiv;
|
34 | if (mouseMoveStatus && hoverEnabled && Utils.isInArray(HoverMode.repulse, hoverMode)) {
|
35 | this.hoverRepulse();
|
36 | }
|
37 | else if (clickEnabled && Utils.isInArray(ClickMode.repulse, clickMode)) {
|
38 | this.clickRepulse();
|
39 | }
|
40 | else {
|
41 | Utils.divModeExecute(DivMode.repulse, divs, (selector, div) => this.singleSelectorRepulse(selector, div));
|
42 | }
|
43 | }
|
44 | singleSelectorRepulse(selector, div) {
|
45 | const container = this.container;
|
46 | const query = document.querySelectorAll(selector);
|
47 | if (!query.length) {
|
48 | return;
|
49 | }
|
50 | query.forEach((item) => {
|
51 | const elem = item;
|
52 | const pxRatio = container.retina.pixelRatio;
|
53 | const pos = {
|
54 | x: (elem.offsetLeft + elem.offsetWidth / 2) * pxRatio,
|
55 | y: (elem.offsetTop + elem.offsetHeight / 2) * pxRatio,
|
56 | };
|
57 | const repulseRadius = (elem.offsetWidth / 2) * pxRatio;
|
58 | const area = div.type === DivType.circle
|
59 | ? new Circle(pos.x, pos.y, repulseRadius)
|
60 | : new Rectangle(elem.offsetLeft * pxRatio, elem.offsetTop * pxRatio, elem.offsetWidth * pxRatio, elem.offsetHeight * pxRatio);
|
61 | const divs = container.actualOptions.interactivity.modes.repulse.divs;
|
62 | const divRepulse = Utils.divMode(divs, elem);
|
63 | this.processRepulse(pos, repulseRadius, area, divRepulse);
|
64 | });
|
65 | }
|
66 | hoverRepulse() {
|
67 | const container = this.container;
|
68 | const mousePos = container.interactivity.mouse.position;
|
69 | if (!mousePos) {
|
70 | return;
|
71 | }
|
72 | const repulseRadius = container.retina.repulseModeDistance;
|
73 | this.processRepulse(mousePos, repulseRadius, new Circle(mousePos.x, mousePos.y, repulseRadius));
|
74 | }
|
75 | processRepulse(position, repulseRadius, area, divRepulse) {
|
76 | var _a;
|
77 | const container = this.container;
|
78 | const query = container.particles.quadTree.query(area);
|
79 | for (const particle of query) {
|
80 | const { dx, dy, distance } = NumberUtils.getDistances(particle.position, position);
|
81 | const normVec = {
|
82 | x: dx / distance,
|
83 | y: dy / distance,
|
84 | };
|
85 | const velocity = ((_a = divRepulse === null || divRepulse === void 0 ? void 0 : divRepulse.speed) !== null && _a !== void 0 ? _a : container.actualOptions.interactivity.modes.repulse.speed) * 100;
|
86 | const repulseFactor = NumberUtils.clamp((1 - Math.pow(distance / repulseRadius, 2)) * velocity, 0, 50);
|
87 | particle.position.x += normVec.x * repulseFactor;
|
88 | particle.position.y += normVec.y * repulseFactor;
|
89 | }
|
90 | }
|
91 | clickRepulse() {
|
92 | const container = this.container;
|
93 | if (!container.repulse.finish) {
|
94 | if (!container.repulse.count) {
|
95 | container.repulse.count = 0;
|
96 | }
|
97 | container.repulse.count++;
|
98 | if (container.repulse.count === container.particles.count) {
|
99 | container.repulse.finish = true;
|
100 | }
|
101 | }
|
102 | if (container.repulse.clicking) {
|
103 | const repulseDistance = container.retina.repulseModeDistance;
|
104 | const repulseRadius = Math.pow(repulseDistance / 6, 3);
|
105 | const mouseClickPos = container.interactivity.mouse.clickPosition;
|
106 | if (mouseClickPos === undefined) {
|
107 | return;
|
108 | }
|
109 | const range = new Circle(mouseClickPos.x, mouseClickPos.y, repulseRadius);
|
110 | const query = container.particles.quadTree.query(range);
|
111 | for (const particle of query) {
|
112 | const { dx, dy, distance } = NumberUtils.getDistances(mouseClickPos, particle.position);
|
113 | const d = distance * distance;
|
114 | if (d <= repulseRadius) {
|
115 | container.repulse.particles.push(particle);
|
116 | const velocity = container.actualOptions.interactivity.modes.repulse.speed;
|
117 | const v = Vector.create(dx, dy);
|
118 | v.length = (-repulseRadius * velocity) / d;
|
119 | particle.velocity.setTo(v);
|
120 | }
|
121 | }
|
122 | }
|
123 | else if (container.repulse.clicking === false) {
|
124 | for (const particle of container.repulse.particles) {
|
125 | particle.velocity.setTo(particle.initialVelocity);
|
126 | }
|
127 | container.repulse.particles = [];
|
128 | }
|
129 | }
|
130 | }
|