tsparticles
Version:
Easily create highly customizable particle 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.
262 lines (261 loc) • 9.89 kB
JavaScript
import { Particle } from "./Particle";
import { NumberUtils, Point, QuadTree, Rectangle, Utils } from "../Utils";
import { InteractionManager } from "./Particle/InteractionManager";
import { ParticlesOptions } from "../Options/Classes/Particles/ParticlesOptions";
export class Particles {
constructor(container) {
this.container = container;
this.nextId = 0;
this.array = [];
this.limit = 0;
this.linksFreq = new Map();
this.trianglesFreq = new Map();
this.interactionManager = new InteractionManager(container);
const canvasSize = this.container.canvas.size;
this.linksColors = new Map();
this.quadTree = new QuadTree(new Rectangle(-canvasSize.width / 4, -canvasSize.height / 4, (canvasSize.width * 3) / 2, (canvasSize.height * 3) / 2), 4);
}
get count() {
return this.array.length;
}
init() {
const container = this.container;
const options = container.actualOptions;
this.linksFreq = new Map();
this.trianglesFreq = new Map();
let handled = false;
for (const particle of options.manualParticles) {
const pos = particle.position
? {
x: (particle.position.x * container.canvas.size.width) / 100,
y: (particle.position.y * container.canvas.size.height) / 100,
}
: undefined;
this.addParticle(pos, particle.options);
}
for (const [, plugin] of container.plugins) {
if (plugin.particlesInitialization !== undefined) {
handled = plugin.particlesInitialization();
}
if (handled) {
break;
}
}
if (!handled) {
for (let i = this.count; i < options.particles.number.value; i++) {
this.addParticle();
}
}
if (options.infection.enable) {
for (let i = 0; i < options.infection.infections; i++) {
const notInfected = this.array.filter((p) => p.infecter.infectionStage === undefined);
const infected = Utils.itemFromArray(notInfected);
infected.infecter.startInfection(0);
}
}
this.interactionManager.init();
container.pathGenerator.init();
}
redraw() {
this.clear();
this.init();
this.draw({ value: 0, factor: 0 });
}
removeAt(index, quantity, override) {
if (index >= 0 && index <= this.count) {
for (const particle of this.array.splice(index, quantity !== null && quantity !== void 0 ? quantity : 1)) {
particle.destroy(override);
}
}
}
remove(particle, override) {
this.removeAt(this.array.indexOf(particle), undefined, override);
}
update(delta) {
const container = this.container;
const particlesToDelete = [];
container.pathGenerator.update();
for (const [, plugin] of container.plugins) {
if (plugin.update !== undefined) {
plugin.update(delta);
}
}
for (const particle of this.array) {
const resizeFactor = this.container.canvas.resizeFactor;
if (resizeFactor) {
particle.position.x *= resizeFactor.width;
particle.position.y *= resizeFactor.height;
}
particle.move(delta);
if (particle.destroyed) {
particlesToDelete.push(particle);
continue;
}
this.quadTree.insert(new Point(particle.getPosition(), particle));
}
for (const particle of particlesToDelete) {
this.remove(particle);
}
this.interactionManager.externalInteract(delta);
for (const particle of this.container.particles.array) {
particle.update(delta);
if (!particle.destroyed && !particle.spawning) {
this.interactionManager.particlesInteract(particle, delta);
}
}
delete container.canvas.resizeFactor;
}
draw(delta) {
const container = this.container;
container.canvas.clear();
const canvasSize = this.container.canvas.size;
this.quadTree = new QuadTree(new Rectangle(-canvasSize.width / 4, -canvasSize.height / 4, (canvasSize.width * 3) / 2, (canvasSize.height * 3) / 2), 4);
this.update(delta);
for (const [, plugin] of container.plugins) {
container.canvas.drawPlugin(plugin, delta);
}
for (const p of this.array) {
p.draw(delta);
}
}
clear() {
this.array = [];
}
push(nb, mouse, overrideOptions) {
const container = this.container;
const options = container.actualOptions;
const limit = options.particles.number.limit * container.density;
this.pushing = true;
if (limit > 0) {
const countToRemove = this.count + nb - limit;
if (countToRemove > 0) {
this.removeQuantity(countToRemove);
}
}
for (let i = 0; i < nb; i++) {
this.addParticle(mouse === null || mouse === void 0 ? void 0 : mouse.position, overrideOptions);
}
this.pushing = false;
}
addParticle(position, overrideOptions) {
return this.pushParticle(position, overrideOptions);
}
addSplitParticle(parent) {
const splitOptions = parent.options.destroy.split;
const options = new ParticlesOptions();
options.load(parent.options);
const factor = NumberUtils.getRangeValue(splitOptions.factor.value);
options.color.load({
value: {
hsl: parent.getFillColor(),
},
});
if (typeof options.size.value === "number") {
options.size.value /= factor;
}
else {
options.size.value.min /= factor;
options.size.value.max /= factor;
}
options.load(splitOptions.particles);
const offset = NumberUtils.setRangeValue(-parent.size.value, parent.size.value);
const position = {
x: parent.position.x + NumberUtils.randomInRange(offset),
y: parent.position.y + NumberUtils.randomInRange(offset),
};
return this.pushParticle(position, options, (particle) => {
if (particle.size.value < 0.5) {
return false;
}
particle.velocity.length = NumberUtils.randomInRange(NumberUtils.setRangeValue(parent.velocity.length, particle.velocity.length));
particle.splitCount = parent.splitCount + 1;
particle.unbreakable = true;
setTimeout(() => {
particle.unbreakable = false;
}, 500);
return true;
});
}
removeQuantity(quantity) {
this.removeAt(0, quantity);
}
getLinkFrequency(p1, p2) {
const key = `${Math.min(p1.id, p2.id)}_${Math.max(p1.id, p2.id)}`;
let res = this.linksFreq.get(key);
if (res === undefined) {
res = Math.random();
this.linksFreq.set(key, res);
}
return res;
}
getTriangleFrequency(p1, p2, p3) {
let [id1, id2, id3] = [p1.id, p2.id, p3.id];
if (id1 > id2) {
[id2, id1] = [id1, id2];
}
if (id2 > id3) {
[id3, id2] = [id2, id3];
}
if (id1 > id3) {
[id3, id1] = [id1, id3];
}
const key = `${id1}_${id2}_${id3}`;
let res = this.trianglesFreq.get(key);
if (res === undefined) {
res = Math.random();
this.trianglesFreq.set(key, res);
}
return res;
}
setDensity() {
const options = this.container.actualOptions;
this.applyDensity(options.particles);
}
applyDensity(options) {
var _a;
if (!((_a = options.number.density) === null || _a === void 0 ? void 0 : _a.enable)) {
return;
}
const numberOptions = options.number;
const densityFactor = this.initDensityFactor(numberOptions.density);
const optParticlesNumber = numberOptions.value;
const optParticlesLimit = numberOptions.limit > 0 ? numberOptions.limit : optParticlesNumber;
const particlesNumber = Math.min(optParticlesNumber, optParticlesLimit) * densityFactor;
const particlesCount = this.count;
this.limit = numberOptions.limit * densityFactor;
if (particlesCount < particlesNumber) {
this.push(Math.abs(particlesNumber - particlesCount), undefined, options);
}
else if (particlesCount > particlesNumber) {
this.removeQuantity(particlesCount - particlesNumber);
}
}
initDensityFactor(densityOptions) {
const container = this.container;
if (!container.canvas.element || !densityOptions.enable) {
return 1;
}
const canvas = container.canvas.element;
const pxRatio = container.retina.pixelRatio;
return (canvas.width * canvas.height) / (densityOptions.factor * pxRatio * pxRatio * densityOptions.area);
}
pushParticle(position, overrideOptions, initializer) {
try {
const particle = new Particle(this.nextId, this.container, position, overrideOptions);
let canAdd = true;
if (initializer) {
canAdd = initializer(particle);
}
if (!canAdd) {
return;
}
this.array.push(particle);
this.nextId++;
return particle;
}
catch (e) {
console.warn(`error adding particle: ${e}`);
return;
}
}
}