UNPKG

@tsparticles/interaction-external-repulse

Version:

tsParticles repulse external interaction

328 lines (319 loc) 19.2 kB
(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); /* External Interaction v4.3.2 */ (function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('@tsparticles/plugin-interactivity'), require('@tsparticles/engine')) : typeof define === 'function' && define.amd ? define(['exports', '@tsparticles/plugin-interactivity', '@tsparticles/engine'], factory) : (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory((global.__tsParticlesInternals = global.__tsParticlesInternals || {}, global.__tsParticlesInternals.interactions = global.__tsParticlesInternals.interactions || {}, global.__tsParticlesInternals.interactions.externalRepulse = global.__tsParticlesInternals.interactions.externalRepulse || {}), global.__tsParticlesInternals.plugins.interactivity, global.__tsParticlesInternals.engine)); })(this, (function (exports, pluginInteractivity, engine) { 'use strict'; class RepulseBase { distance = 200; duration = 0.4; easing = "ease-out-quad"; factor = 100; maxSpeed = 50; restore; speed = 1; constructor() { this.restore = { enable: false, delay: 0, speed: 0.08, follow: true, }; } load(data) { if (engine.isNull(data)) { return; } engine.loadProperty(this, "distance", data.distance); engine.loadProperty(this, "duration", data.duration); engine.loadProperty(this, "easing", data.easing); engine.loadProperty(this, "factor", data.factor); engine.loadProperty(this, "speed", data.speed); engine.loadProperty(this, "maxSpeed", data.maxSpeed); if (data.restore !== undefined) { this.restore.enable = data.restore.enable ?? this.restore.enable; this.restore.delay = data.restore.delay ?? this.restore.delay; this.restore.speed = data.restore.speed ?? this.restore.speed; this.restore.follow = data.restore.follow ?? this.restore.follow; } } } class RepulseDiv extends RepulseBase { selectors = []; load(data) { super.load(data); if (engine.isNull(data)) { return; } engine.loadProperty(this, "selectors", data.selectors); } } class Repulse extends RepulseBase { divs; load(data) { super.load(data); if (engine.isNull(data)) { return; } this.divs = engine.executeOnSingleOrMultiple(data.divs, div => { const tmp = new RepulseDiv(); tmp.load(div); return tmp; }); } } const repulseMode = "repulse", minDistance = 0, repulseRadiusFactor = 6, repulseRadiusPower = 3, squarePower = 2, minRadius = 0, minSpeed = 0, easingOffset = 1, minRestoreSpeed = 0.001, maxRestoreSpeed = 1, restoreEpsilon = 0.5; class Repulser extends pluginInteractivity.ExternalInteractorBase { handleClickMode; #clickVec; #interactedThisFrame; #maxDistance; #normVec; #pluginManager; #restoreData; constructor(pluginManager, container) { super(container); this.#pluginManager = pluginManager; this.#maxDistance = 0; this.#normVec = engine.Vector.origin; this.#interactedThisFrame = new Set(); this.#clickVec = engine.Vector.origin; this.#restoreData = new Map(); container.repulse ??= { particles: [] }; this.handleClickMode = (mode, interactivityData) => { const options = this.container.actualOptions, repulseOpts = options.interactivity?.modes.repulse; if (!repulseOpts || mode !== repulseMode) { return; } container.repulse ??= { particles: [] }; const repulse = container.repulse; repulse.clicking = true; repulse.count = 0; for (const particle of container.repulse.particles) { if (!this.isEnabled(interactivityData, particle)) { continue; } particle.velocity.setTo(particle.initialVelocity); } repulse.particles = []; repulse.finish = false; setTimeout(() => { if (container.destroyed) { return; } repulse.clicking = false; }, repulseOpts.duration * engine.millisecondsToSeconds); }; } get maxDistance() { return this.#maxDistance; } clear() { } init() { const container = this.container, repulse = container.actualOptions.interactivity?.modes.repulse; if (!repulse) { return; } this.#maxDistance = repulse.distance; container.retina.repulseModeDistance = repulse.distance * container.retina.pixelRatio; } interact(interactivityData) { this.#interactedThisFrame.clear(); const container = this.container, options = container.actualOptions, mouseMoveStatus = interactivityData.status === pluginInteractivity.mouseMoveEvent, events = options.interactivity?.events; if (!events) { return; } const hover = events.onHover, hoverEnabled = hover.enable, hoverMode = hover.mode, click = events.onClick, clickEnabled = click.enable, clickMode = click.mode, divs = events.onDiv; if (mouseMoveStatus && hoverEnabled && engine.isInArray(repulseMode, hoverMode)) { this.#hoverRepulse(interactivityData); } else if (clickEnabled && engine.isInArray(repulseMode, clickMode)) { this.#clickRepulse(interactivityData); } else { pluginInteractivity.divModeExecute(repulseMode, divs, (selector, div) => { this.#singleSelectorRepulse(interactivityData, selector, div); }); } this.#restoreParticles(); } isEnabled(interactivityData, particle) { const container = this.container, options = container.actualOptions, mouse = interactivityData.mouse, events = (particle?.interactivity ?? options.interactivity)?.events; if (!events) { return false; } const divs = events.onDiv, hover = events.onHover, click = events.onClick, divRepulse = pluginInteractivity.isDivModeEnabled(repulseMode, divs); if (!(divRepulse || (hover.enable && !!mouse.position) || (click.enable && mouse.clickPosition))) { return false; } const hoverMode = hover.mode, clickMode = click.mode; return engine.isInArray(repulseMode, hoverMode) || engine.isInArray(repulseMode, clickMode) || divRepulse; } loadModeOptions(options, ...sources) { engine.loadOptionProperty(options, "repulse", Repulse, ...sources); } reset() { } #clickRepulse(interactivityData) { const container = this.container, repulseOptions = container.actualOptions.interactivity?.modes.repulse; if (!repulseOptions) { return; } const repulse = container.repulse ?? { particles: [] }; if (!repulse.finish) { repulse.count ??= 0; repulse.count++; if (repulse.count === container.particles.count) { repulse.finish = true; } } if (repulse.clicking) { const repulseDistance = container.retina.repulseModeDistance; if (!repulseDistance || repulseDistance < minDistance) { return; } const repulseRadius = Math.pow(repulseDistance / repulseRadiusFactor, repulseRadiusPower), mouseClickPos = interactivityData.mouse.clickPosition; if (mouseClickPos === undefined) { return; } const range = new engine.Circle(mouseClickPos.x, mouseClickPos.y, repulseRadius), query = container.particles.grid.query(range, p => this.isEnabled(interactivityData, p)); for (const particle of query) { const { dx, dy, distance } = engine.getDistances(mouseClickPos, particle.position), d = distance ** squarePower, velocity = repulseOptions.speed, force = (-repulseRadius * velocity) / d; if (d <= repulseRadius) { this.#trackInteractedParticle(particle); repulse.particles.push(particle); this.#clickVec.x = dx; this.#clickVec.y = dy; this.#clickVec.length = force; particle.velocity.setTo(this.#clickVec); } } } else if (repulse.clicking === false) { for (const particle of repulse.particles) { particle.velocity.setTo(particle.initialVelocity); } repulse.particles = []; } } #hoverRepulse(interactivityData) { const container = this.container, mousePos = interactivityData.mouse.position, repulseRadius = container.retina.repulseModeDistance; if (!repulseRadius || repulseRadius < minRadius || !mousePos) { return; } this.#processRepulse(interactivityData, mousePos, repulseRadius, new engine.Circle(mousePos.x, mousePos.y, repulseRadius)); } #processRepulse(interactivityData, position, repulseRadius, area, divRepulse) { const container = this.container, query = container.particles.grid.query(area, p => this.isEnabled(interactivityData, p)), repulseOptions = container.actualOptions.interactivity?.modes.repulse; if (!repulseOptions) { return; } const { easing, speed, factor, maxSpeed } = repulseOptions, easingFunc = this.#pluginManager.getEasing(easing), velocity = (divRepulse?.speed ?? speed) * factor; for (const particle of query) { const { dx, dy, distance } = engine.getDistances(particle.position, position), repulseFactor = engine.clamp(easingFunc(easingOffset - distance / repulseRadius) * velocity, minSpeed, maxSpeed); this.#normVec.x = !distance ? velocity : (dx / distance) * repulseFactor; this.#normVec.y = !distance ? velocity : (dy / distance) * repulseFactor; this.#trackInteractedParticle(particle); particle.position.addTo(this.#normVec); } } #restoreParticles() { const restore = this.container.actualOptions.interactivity?.modes.repulse?.restore; if (!restore?.enable || !this.#restoreData.size) { return; } const now = Date.now(), restoreDelay = restore.delay * engine.millisecondsToSeconds, restoreSpeed = Math.max(minRestoreSpeed, Math.min(maxRestoreSpeed, restore.speed)); for (const [particle, restoreData] of this.#restoreData) { if (this.#interactedThisFrame.has(particle)) { continue; } if (particle.destroyed) { this.#restoreData.delete(particle); continue; } const target = restoreData.target; if (now - restoreData.lastInteractionTime < restoreDelay) { continue; } if (restore.follow && particle.options.move.enable) { target.x += particle.velocity.x; target.y += particle.velocity.y; target.z += particle.velocity.z; } const dx = target.x - particle.position.x, dy = target.y - particle.position.y, dz = target.z - particle.position.z; particle.position.x += dx * restoreSpeed; particle.position.y += dy * restoreSpeed; particle.position.z += dz * restoreSpeed; if (Math.abs(dx) <= restoreEpsilon && Math.abs(dy) <= restoreEpsilon) { particle.position.x = target.x; particle.position.y = target.y; particle.position.z = target.z; this.#restoreData.delete(particle); continue; } } } #singleSelectorRepulse(interactivityData, selector, div) { const container = this.container, repulse = container.actualOptions.interactivity?.modes.repulse; if (!repulse) { return; } const query = engine.safeDocument().querySelectorAll(selector); if (!query.length) { return; } query.forEach(item => { const elem = item, pxRatio = container.retina.pixelRatio, pos = { x: (elem.offsetLeft + elem.offsetWidth * engine.half) * pxRatio, y: (elem.offsetTop + elem.offsetHeight * engine.half) * pxRatio, }, repulseRadius = elem.offsetWidth * engine.half * pxRatio, area = div.type === pluginInteractivity.DivType.circle ? new engine.Circle(pos.x, pos.y, repulseRadius) : new engine.Rectangle(elem.offsetLeft * pxRatio, elem.offsetTop * pxRatio, elem.offsetWidth * pxRatio, elem.offsetHeight * pxRatio), divs = repulse.divs, divRepulse = pluginInteractivity.divMode(divs, elem); this.#processRepulse(interactivityData, pos, repulseRadius, area, divRepulse); }); } #trackInteractedParticle(particle) { this.#interactedThisFrame.add(particle); const restore = this.container.actualOptions.interactivity?.modes.repulse?.restore; if (!restore?.enable) { return; } const now = Date.now(); let restoreData = this.#restoreData.get(particle); if (!restoreData) { restoreData = { target: particle.position.copy(), lastInteractionTime: now, }; this.#restoreData.set(particle, restoreData); } restoreData.lastInteractionTime = now; if (restore.follow && particle.options.move.enable) { restoreData.target.x += particle.velocity.x; restoreData.target.y += particle.velocity.y; restoreData.target.z += particle.velocity.z; } } } async function loadExternalRepulseInteraction(engine) { engine.checkVersion("4.3.2"); await engine.pluginManager.register((e) => { pluginInteractivity.ensureInteractivityPluginLoaded(e); const pluginManager = e.pluginManager; pluginManager.addInteractor?.("externalRepulse", container => { return Promise.resolve(new Repulser(pluginManager, container)); }); }); } const globalObject = globalThis; globalObject.__tsParticlesInternals = globalObject.__tsParticlesInternals ?? {}; globalObject.loadExternalRepulseInteraction = loadExternalRepulseInteraction; exports.Repulse = Repulse; exports.RepulseBase = RepulseBase; exports.RepulseDiv = RepulseDiv; exports.loadExternalRepulseInteraction = loadExternalRepulseInteraction; })); Object.assign(globalThis.window || globalThis, { loadExternalRepulseInteraction: (globalThis.__tsParticlesInternals.interactions.externalRepulse || {}).loadExternalRepulseInteraction }); delete (globalThis.window || globalThis).tsparticlesInternalExports;