UNPKG

tsparticles-engine

Version:

Easily create highly customizable particle, confetti and fireworks animations and use them as animated backgrounds for your website. Ready to use components available also for React, Vue.js (2.x and 3.x), Angular, Svelte, jQuery, Preact, Riot.js, Inferno.

275 lines (274 loc) 11.2 kB
import { getLogger, getPosition } from "../Utils/Utils"; import { InteractionManager } from "./Utils/InteractionManager"; import { Particle } from "./Particle"; import { Point } from "./Utils/Point"; import { QuadTree } from "./Utils/QuadTree"; import { Rectangle } from "./Utils/Rectangle"; import { errorPrefix } from "./Utils/Constants"; const qTreeCapacity = 4; const qTreeRectangle = (canvasSize) => { return new Rectangle(-canvasSize.width / 4, -canvasSize.height / 4, (canvasSize.width * 3) / 2, (canvasSize.height * 3) / 2); }; export class Particles { constructor(engine, container) { this._applyDensity = (options, manualCount, group) => { if (!options.number.density?.enable) { return; } const numberOptions = options.number, densityFactor = this._initDensityFactor(numberOptions.density), optParticlesNumber = numberOptions.value, optParticlesLimit = numberOptions.limit > 0 ? numberOptions.limit : optParticlesNumber, particlesNumber = Math.min(optParticlesNumber, optParticlesLimit) * densityFactor + manualCount, particlesCount = Math.min(this.count, this.filter((t) => t.group === group).length); this.limit = numberOptions.limit * densityFactor; if (particlesCount < particlesNumber) { this.push(Math.abs(particlesNumber - particlesCount), undefined, options, group); } else if (particlesCount > particlesNumber) { this.removeQuantity(particlesCount - particlesNumber, group); } }; this._initDensityFactor = (densityOptions) => { const container = this._container; if (!container.canvas.element || !densityOptions.enable) { return 1; } const canvas = container.canvas.element, pxRatio = container.retina.pixelRatio; return (canvas.width * canvas.height) / (densityOptions.factor * pxRatio ** 2 * densityOptions.area); }; this._pushParticle = (position, overrideOptions, group, initializer) => { try { let particle = this.pool.pop(); if (particle) { particle.init(this._nextId, position, overrideOptions, group); } else { particle = new Particle(this._engine, this._nextId, this._container, position, overrideOptions, group); } let canAdd = true; if (initializer) { canAdd = initializer(particle); } if (!canAdd) { return; } this._array.push(particle); this._zArray.push(particle); this._nextId++; this._engine.dispatchEvent("particleAdded", { container: this._container, data: { particle, }, }); return particle; } catch (e) { getLogger().warning(`${errorPrefix} adding particle: ${e}`); return; } }; this._removeParticle = (index, group, override) => { const particle = this._array[index]; if (!particle || particle.group !== group) { return false; } particle.destroy(override); const zIdx = this._zArray.indexOf(particle); this._array.splice(index, 1); this._zArray.splice(zIdx, 1); this.pool.push(particle); this._engine.dispatchEvent("particleRemoved", { container: this._container, data: { particle, }, }); return true; }; this._engine = engine; this._container = container; this._nextId = 0; this._array = []; this._zArray = []; this.pool = []; this.limit = 0; this.needsSort = false; this.lastZIndex = 0; this._interactionManager = new InteractionManager(engine, container); const canvasSize = container.canvas.size; this.quadTree = new QuadTree(qTreeRectangle(canvasSize), qTreeCapacity); this.movers = this._engine.plugins.getMovers(container, true); this.updaters = this._engine.plugins.getUpdaters(container, true); } get count() { return this._array.length; } addManualParticles() { const container = this._container, options = container.actualOptions; for (const particle of options.manualParticles) { this.addParticle(particle.position ? getPosition(particle.position, container.canvas.size) : undefined, particle.options); } } addParticle(position, overrideOptions, group, initializer) { const container = this._container, options = container.actualOptions, limit = options.particles.number.limit; if (limit > 0) { const countToRemove = this.count + 1 - limit; if (countToRemove > 0) { this.removeQuantity(countToRemove); } } return this._pushParticle(position, overrideOptions, group, initializer); } clear() { this._array = []; this._zArray = []; } destroy() { this._array = []; this._zArray = []; this.movers = []; this.updaters = []; } async draw(delta) { const container = this._container; container.canvas.clear(); await this.update(delta); for (const [, plugin] of container.plugins) { container.canvas.drawPlugin(plugin, delta); } for (const p of this._zArray) { p.draw(delta); } } filter(condition) { return this._array.filter(condition); } find(condition) { return this._array.find(condition); } handleClickMode(mode) { this._interactionManager.handleClickMode(mode); } init() { const container = this._container, options = container.actualOptions; this.lastZIndex = 0; this.needsSort = false; let handled = false; this.updaters = this._engine.plugins.getUpdaters(container, true); this._interactionManager.init(); for (const [, plugin] of container.plugins) { if (plugin.particlesInitialization !== undefined) { handled = plugin.particlesInitialization(); } if (handled) { break; } } this._interactionManager.init(); for (const [, pathGenerator] of container.pathGenerators) { pathGenerator.init(container); } this.addManualParticles(); if (!handled) { for (const group in options.particles.groups) { const groupOptions = options.particles.groups[group]; for (let i = this.count, j = 0; j < groupOptions.number?.value && i < options.particles.number.value; i++, j++) { this.addParticle(undefined, groupOptions, group); } } for (let i = this.count; i < options.particles.number.value; i++) { this.addParticle(); } } } push(nb, mouse, overrideOptions, group) { this.pushing = true; for (let i = 0; i < nb; i++) { this.addParticle(mouse?.position, overrideOptions, group); } this.pushing = false; } async redraw() { this.clear(); this.init(); await this.draw({ value: 0, factor: 0 }); } remove(particle, group, override) { this.removeAt(this._array.indexOf(particle), undefined, group, override); } removeAt(index, quantity = 1, group, override) { if (index < 0 || index > this.count) { return; } let deleted = 0; for (let i = index; deleted < quantity && i < this.count; i++) { this._removeParticle(i--, group, override) && deleted++; } } removeQuantity(quantity, group) { this.removeAt(0, quantity, group); } setDensity() { const options = this._container.actualOptions, groups = options.particles.groups; for (const group in groups) { this._applyDensity(groups[group], 0, group); } this._applyDensity(options.particles, options.manualParticles.length); } async update(delta) { const container = this._container, particlesToDelete = new Set(); this.quadTree = new QuadTree(qTreeRectangle(container.canvas.size), qTreeCapacity); for (const [, pathGenerator] of container.pathGenerators) { pathGenerator.update(); } for (const [, plugin] of container.plugins) { plugin.update && plugin.update(delta); } for (const particle of this._array) { const resizeFactor = container.canvas.resizeFactor; if (resizeFactor && !particle.ignoresResizeRatio) { particle.position.x *= resizeFactor.width; particle.position.y *= resizeFactor.height; particle.initialPosition.x *= resizeFactor.width; particle.initialPosition.y *= resizeFactor.height; } particle.ignoresResizeRatio = false; await this._interactionManager.reset(particle); for (const [, plugin] of this._container.plugins) { if (particle.destroyed) { break; } if (plugin.particleUpdate) { plugin.particleUpdate(particle, delta); } } for (const mover of this.movers) { mover.isEnabled(particle) && mover.move(particle, delta); } if (particle.destroyed) { particlesToDelete.add(particle); continue; } this.quadTree.insert(new Point(particle.getPosition(), particle)); } if (particlesToDelete.size) { const checkDelete = (p) => !particlesToDelete.has(p); this._array = this.filter(checkDelete); this._zArray = this._zArray.filter(checkDelete); this.pool.push(...particlesToDelete); } await this._interactionManager.externalInteract(delta); for (const particle of this._array) { for (const updater of this.updaters) { updater.update(particle, delta); } if (!particle.destroyed && !particle.spawning) { await this._interactionManager.particlesInteract(particle, delta); } } delete container.canvas.resizeFactor; if (this.needsSort) { const zArray = this._zArray; zArray.sort((a, b) => b.position.z - a.position.z || a.id - b.id); this.lastZIndex = zArray[zArray.length - 1].position.z; this.needsSort = false; } } }