@tsparticles/interaction-external-bubble
Version:
tsParticles bubble external interaction
290 lines (289 loc) • 15.6 kB
JavaScript
(function (factory) {
if (typeof module === "object" && typeof module.exports === "object") {
var v = factory(require, exports);
if (v !== undefined) module.exports = v;
}
else if (typeof define === "function" && define.amd) {
define(["require", "exports", "@tsparticles/engine", "./Options/Classes/Bubble.js", "./Enums.js", "./Utils.js"], factory);
}
})(function (require, exports) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.Bubbler = void 0;
const engine_1 = require("@tsparticles/engine");
const Bubble_js_1 = require("./Options/Classes/Bubble.js");
const Enums_js_1 = require("./Enums.js");
const Utils_js_1 = require("./Utils.js");
const bubbleMode = "bubble", minDistance = 0, defaultClickTime = 0, double = 2, defaultOpacity = 1, ratioOffset = 1, defaultBubbleValue = 0, minRatio = 0, half = 0.5, defaultRatio = 1;
class Bubbler extends engine_1.ExternalInteractorBase {
constructor(container, engine) {
super(container);
this._clickBubble = () => {
const container = this.container, options = container.actualOptions, mouseClickPos = container.interactivity.mouse.clickPosition, bubbleOptions = options.interactivity.modes.bubble;
if (!bubbleOptions || !mouseClickPos) {
return;
}
if (!container.bubble) {
container.bubble = {};
}
const distance = container.retina.bubbleModeDistance;
if (!distance || distance < minDistance) {
return;
}
const query = container.particles.quadTree.queryCircle(mouseClickPos, distance, p => this.isEnabled(p)), { bubble } = container;
for (const particle of query) {
if (!bubble.clicking) {
continue;
}
particle.bubble.inRange = !bubble.durationEnd;
const pos = particle.getPosition(), distMouse = (0, engine_1.getDistance)(pos, mouseClickPos), timeSpent = (new Date().getTime() - (container.interactivity.mouse.clickTime ?? defaultClickTime)) /
engine_1.millisecondsToSeconds;
if (timeSpent > bubbleOptions.duration) {
bubble.durationEnd = true;
}
if (timeSpent > bubbleOptions.duration * double) {
bubble.clicking = false;
bubble.durationEnd = false;
}
const sizeData = {
bubbleObj: {
optValue: container.retina.bubbleModeSize,
value: particle.bubble.radius,
},
particlesObj: {
optValue: (0, engine_1.getRangeMax)(particle.options.size.value) * container.retina.pixelRatio,
value: particle.size.value,
},
type: Enums_js_1.ProcessBubbleType.size,
};
this._process(particle, distMouse, timeSpent, sizeData);
const opacityData = {
bubbleObj: {
optValue: bubbleOptions.opacity,
value: particle.bubble.opacity,
},
particlesObj: {
optValue: (0, engine_1.getRangeMax)(particle.options.opacity.value),
value: particle.opacity?.value ?? defaultOpacity,
},
type: Enums_js_1.ProcessBubbleType.opacity,
};
this._process(particle, distMouse, timeSpent, opacityData);
if (!bubble.durationEnd && distMouse <= distance) {
this._hoverBubbleColor(particle, distMouse);
}
else {
delete particle.bubble.color;
}
}
};
this._hoverBubble = () => {
const container = this.container, mousePos = container.interactivity.mouse.position, distance = container.retina.bubbleModeDistance;
if (!distance || distance < minDistance || !mousePos) {
return;
}
const query = container.particles.quadTree.queryCircle(mousePos, distance, p => this.isEnabled(p));
for (const particle of query) {
particle.bubble.inRange = true;
const pos = particle.getPosition(), pointDistance = (0, engine_1.getDistance)(pos, mousePos), ratio = ratioOffset - pointDistance / distance;
if (pointDistance <= distance) {
if (ratio >= minRatio && container.interactivity.status === engine_1.mouseMoveEvent) {
this._hoverBubbleSize(particle, ratio);
this._hoverBubbleOpacity(particle, ratio);
this._hoverBubbleColor(particle, ratio);
}
}
else {
this.reset(particle);
}
if (container.interactivity.status === engine_1.mouseLeaveEvent) {
this.reset(particle);
}
}
};
this._hoverBubbleColor = (particle, ratio, divBubble) => {
const options = this.container.actualOptions, bubbleOptions = divBubble ?? options.interactivity.modes.bubble;
if (!bubbleOptions) {
return;
}
if (!particle.bubble.finalColor) {
const modeColor = bubbleOptions.color;
if (!modeColor) {
return;
}
const bubbleColor = (0, engine_1.itemFromSingleOrMultiple)(modeColor);
particle.bubble.finalColor = (0, engine_1.rangeColorToHsl)(this._engine, bubbleColor);
}
if (!particle.bubble.finalColor) {
return;
}
if (bubbleOptions.mix) {
particle.bubble.color = undefined;
const pColor = particle.getFillColor();
particle.bubble.color = pColor
? (0, engine_1.rgbToHsl)((0, engine_1.colorMix)(pColor, particle.bubble.finalColor, ratioOffset - ratio, ratio))
: particle.bubble.finalColor;
}
else {
particle.bubble.color = particle.bubble.finalColor;
}
};
this._hoverBubbleOpacity = (particle, ratio, divBubble) => {
const container = this.container, options = container.actualOptions, modeOpacity = divBubble?.opacity ?? options.interactivity.modes.bubble?.opacity;
if (!modeOpacity) {
return;
}
const optOpacity = particle.options.opacity.value, pOpacity = particle.opacity?.value ?? defaultOpacity, opacity = (0, Utils_js_1.calculateBubbleValue)(pOpacity, modeOpacity, (0, engine_1.getRangeMax)(optOpacity), ratio);
if (opacity !== undefined) {
particle.bubble.opacity = opacity;
}
};
this._hoverBubbleSize = (particle, ratio, divBubble) => {
const container = this.container, modeSize = divBubble?.size ? divBubble.size * container.retina.pixelRatio : container.retina.bubbleModeSize;
if (modeSize === undefined) {
return;
}
const optSize = (0, engine_1.getRangeMax)(particle.options.size.value) * container.retina.pixelRatio, pSize = particle.size.value, size = (0, Utils_js_1.calculateBubbleValue)(pSize, modeSize, optSize, ratio);
if (size !== undefined) {
particle.bubble.radius = size;
}
};
this._process = (particle, distMouse, timeSpent, data) => {
const container = this.container, bubbleParam = data.bubbleObj.optValue, options = container.actualOptions, bubbleOptions = options.interactivity.modes.bubble;
if (!bubbleOptions || bubbleParam === undefined) {
return;
}
const bubbleDuration = bubbleOptions.duration, bubbleDistance = container.retina.bubbleModeDistance, particlesParam = data.particlesObj.optValue, pObjBubble = data.bubbleObj.value, pObj = data.particlesObj.value ?? defaultBubbleValue, type = data.type;
if (!bubbleDistance || bubbleDistance < minDistance || bubbleParam === particlesParam) {
return;
}
if (!container.bubble) {
container.bubble = {};
}
if (container.bubble.durationEnd) {
if (pObjBubble) {
if (type === Enums_js_1.ProcessBubbleType.size) {
delete particle.bubble.radius;
}
if (type === Enums_js_1.ProcessBubbleType.opacity) {
delete particle.bubble.opacity;
}
}
}
else {
if (distMouse <= bubbleDistance) {
const obj = pObjBubble ?? pObj;
if (obj !== bubbleParam) {
const value = pObj - (timeSpent * (pObj - bubbleParam)) / bubbleDuration;
if (type === Enums_js_1.ProcessBubbleType.size) {
particle.bubble.radius = value;
}
if (type === Enums_js_1.ProcessBubbleType.opacity) {
particle.bubble.opacity = value;
}
}
}
else {
if (type === Enums_js_1.ProcessBubbleType.size) {
delete particle.bubble.radius;
}
if (type === Enums_js_1.ProcessBubbleType.opacity) {
delete particle.bubble.opacity;
}
}
}
};
this._singleSelectorHover = (delta, selector, div) => {
const container = this.container, selectors = document.querySelectorAll(selector), bubble = container.actualOptions.interactivity.modes.bubble;
if (!bubble || !selectors.length) {
return;
}
selectors.forEach(item => {
const elem = item, pxRatio = container.retina.pixelRatio, pos = {
x: (elem.offsetLeft + elem.offsetWidth * half) * pxRatio,
y: (elem.offsetTop + elem.offsetHeight * half) * pxRatio,
}, repulseRadius = elem.offsetWidth * half * pxRatio, area = div.type === engine_1.DivType.circle
? new engine_1.Circle(pos.x, pos.y, repulseRadius)
: new engine_1.Rectangle(elem.offsetLeft * pxRatio, elem.offsetTop * pxRatio, elem.offsetWidth * pxRatio, elem.offsetHeight * pxRatio), query = container.particles.quadTree.query(area, p => this.isEnabled(p));
for (const particle of query) {
if (!area.contains(particle.getPosition())) {
continue;
}
particle.bubble.inRange = true;
const divs = bubble.divs, divBubble = (0, engine_1.divMode)(divs, elem);
if (!particle.bubble.div || particle.bubble.div !== elem) {
this.clear(particle, delta, true);
particle.bubble.div = elem;
}
this._hoverBubbleSize(particle, defaultRatio, divBubble);
this._hoverBubbleOpacity(particle, defaultRatio, divBubble);
this._hoverBubbleColor(particle, defaultRatio, divBubble);
}
});
};
this._engine = engine;
if (!container.bubble) {
container.bubble = {};
}
this.handleClickMode = (mode) => {
if (mode !== bubbleMode) {
return;
}
if (!container.bubble) {
container.bubble = {};
}
container.bubble.clicking = true;
};
}
clear(particle, delta, force) {
if (particle.bubble.inRange && !force) {
return;
}
delete particle.bubble.div;
delete particle.bubble.opacity;
delete particle.bubble.radius;
delete particle.bubble.color;
}
init() {
const container = this.container, bubble = container.actualOptions.interactivity.modes.bubble;
if (!bubble) {
return;
}
container.retina.bubbleModeDistance = bubble.distance * container.retina.pixelRatio;
if (bubble.size !== undefined) {
container.retina.bubbleModeSize = bubble.size * container.retina.pixelRatio;
}
}
interact(delta) {
const options = this.container.actualOptions, events = options.interactivity.events, onHover = events.onHover, onClick = events.onClick, hoverEnabled = onHover.enable, hoverMode = onHover.mode, clickEnabled = onClick.enable, clickMode = onClick.mode, divs = events.onDiv;
if (hoverEnabled && (0, engine_1.isInArray)(bubbleMode, hoverMode)) {
this._hoverBubble();
}
else if (clickEnabled && (0, engine_1.isInArray)(bubbleMode, clickMode)) {
this._clickBubble();
}
else {
(0, engine_1.divModeExecute)(bubbleMode, divs, (selector, div) => this._singleSelectorHover(delta, selector, div));
}
}
isEnabled(particle) {
const container = this.container, options = container.actualOptions, mouse = container.interactivity.mouse, events = (particle?.interactivity ?? options.interactivity).events, { onClick, onDiv, onHover } = events, divBubble = (0, engine_1.isDivModeEnabled)(bubbleMode, onDiv);
if (!(divBubble || (onHover.enable && !!mouse.position) || (onClick.enable && mouse.clickPosition))) {
return false;
}
return (0, engine_1.isInArray)(bubbleMode, onHover.mode) || (0, engine_1.isInArray)(bubbleMode, onClick.mode) || divBubble;
}
loadModeOptions(options, ...sources) {
if (!options.bubble) {
options.bubble = new Bubble_js_1.Bubble();
}
for (const source of sources) {
options.bubble.load(source?.bubble);
}
}
reset(particle) {
particle.bubble.inRange = false;
}
}
exports.Bubbler = Bubbler;
});