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.
282 lines (281 loc) • 9.31 kB
JavaScript
import { errorPrefix, generatedAttribute } from "./Utils/Constants";
import { getLogger, isBoolean, isFunction, isNumber, isString, itemFromSingleOrMultiple } from "../Utils/Utils";
import { Container } from "./Container";
import { EventDispatcher } from "../Utils/EventDispatcher";
import { Plugins } from "./Utils/Plugins";
import { getRandom } from "../Utils/NumberUtils";
async function getDataFromUrl(data) {
const url = itemFromSingleOrMultiple(data.url, data.index);
if (!url) {
return data.fallback;
}
const response = await fetch(url);
if (response.ok) {
return response.json();
}
getLogger().error(`${errorPrefix} ${response.status} while retrieving config file`);
return data.fallback;
}
function isParamsEmpty(params) {
return !params.id && !params.element && !params.url && !params.options;
}
function isParams(obj) {
return !isParamsEmpty(obj);
}
export class Engine {
constructor() {
this._configs = new Map();
this._domArray = [];
this._eventDispatcher = new EventDispatcher();
this._initialized = false;
this.plugins = new Plugins(this);
}
get configs() {
const res = {};
for (const [name, config] of this._configs) {
res[name] = config;
}
return res;
}
get version() {
return "2.12.0";
}
addConfig(nameOrConfig, config) {
if (isString(nameOrConfig)) {
if (config) {
config.name = nameOrConfig;
this._configs.set(nameOrConfig, config);
}
}
else {
this._configs.set(nameOrConfig.name ?? "default", nameOrConfig);
}
}
addEventListener(type, listener) {
this._eventDispatcher.addEventListener(type, listener);
}
async addInteractor(name, interactorInitializer, refresh = true) {
this.plugins.addInteractor(name, interactorInitializer);
await this.refresh(refresh);
}
async addMover(name, moverInitializer, refresh = true) {
this.plugins.addParticleMover(name, moverInitializer);
await this.refresh(refresh);
}
async addParticleUpdater(name, updaterInitializer, refresh = true) {
this.plugins.addParticleUpdater(name, updaterInitializer);
await this.refresh(refresh);
}
async addPathGenerator(name, generator, refresh = true) {
this.plugins.addPathGenerator(name, generator);
await this.refresh(refresh);
}
async addPlugin(plugin, refresh = true) {
this.plugins.addPlugin(plugin);
await this.refresh(refresh);
}
async addPreset(preset, options, override = false, refresh = true) {
this.plugins.addPreset(preset, options, override);
await this.refresh(refresh);
}
async addShape(shape, drawer, initOrRefresh, afterEffectOrRefresh, destroyOrRefresh, refresh = true) {
let customDrawer;
let realRefresh = refresh, realInit, realAfterEffect, realDestroy;
if (isBoolean(initOrRefresh)) {
realRefresh = initOrRefresh;
realInit = undefined;
}
else {
realInit = initOrRefresh;
}
if (isBoolean(afterEffectOrRefresh)) {
realRefresh = afterEffectOrRefresh;
realAfterEffect = undefined;
}
else {
realAfterEffect = afterEffectOrRefresh;
}
if (isBoolean(destroyOrRefresh)) {
realRefresh = destroyOrRefresh;
realDestroy = undefined;
}
else {
realDestroy = destroyOrRefresh;
}
if (isFunction(drawer)) {
customDrawer = {
afterEffect: realAfterEffect,
destroy: realDestroy,
draw: drawer,
init: realInit,
};
}
else {
customDrawer = drawer;
}
this.plugins.addShapeDrawer(shape, customDrawer);
await this.refresh(realRefresh);
}
dispatchEvent(type, args) {
this._eventDispatcher.dispatchEvent(type, args);
}
dom() {
return this._domArray;
}
domItem(index) {
const dom = this.dom(), item = dom[index];
if (!item || item.destroyed) {
dom.splice(index, 1);
return;
}
return item;
}
init() {
if (this._initialized) {
return;
}
this._initialized = true;
}
async load(tagIdOrOptionsOrParams, options) {
return this.loadFromArray(tagIdOrOptionsOrParams, options);
}
async loadFromArray(tagIdOrOptionsOrParams, optionsOrIndex, index) {
let params;
if (!isParams(tagIdOrOptionsOrParams)) {
params = {};
if (isString(tagIdOrOptionsOrParams)) {
params.id = tagIdOrOptionsOrParams;
}
else {
params.options = tagIdOrOptionsOrParams;
}
if (isNumber(optionsOrIndex)) {
params.index = optionsOrIndex;
}
else {
params.options = optionsOrIndex ?? params.options;
}
params.index = index ?? params.index;
}
else {
params = tagIdOrOptionsOrParams;
}
return this._loadParams(params);
}
async loadJSON(tagId, pathConfigJson, index) {
let url, id;
if (isNumber(pathConfigJson) || pathConfigJson === undefined) {
url = tagId;
}
else {
id = tagId;
url = pathConfigJson;
}
return this._loadParams({ id: id, url, index });
}
async refresh(refresh = true) {
if (!refresh) {
return;
}
this.dom().forEach((t) => t.refresh());
}
removeEventListener(type, listener) {
this._eventDispatcher.removeEventListener(type, listener);
}
async set(id, element, options, index) {
const params = { index };
if (isString(id)) {
params.id = id;
}
else {
params.element = id;
}
if (element instanceof HTMLElement) {
params.element = element;
}
else {
params.options = element;
}
if (isNumber(options)) {
params.index = options;
}
else {
params.options = options ?? params.options;
}
return this._loadParams(params);
}
async setJSON(id, element, pathConfigJson, index) {
const params = {};
if (id instanceof HTMLElement) {
params.element = id;
params.url = element;
params.index = pathConfigJson;
}
else {
params.id = id;
params.element = element;
params.url = pathConfigJson;
params.index = index;
}
return this._loadParams(params);
}
setOnClickHandler(callback) {
const dom = this.dom();
if (!dom.length) {
throw new Error(`${errorPrefix} can only set click handlers after calling tsParticles.load()`);
}
for (const domItem of dom) {
domItem.addClickHandler(callback);
}
}
async _loadParams(params) {
const id = params.id ?? `tsparticles${Math.floor(getRandom() * 10000)}`, { index, url } = params, options = url ? await getDataFromUrl({ fallback: params.options, url, index }) : params.options;
let domContainer = params.element ?? document.getElementById(id);
if (!domContainer) {
domContainer = document.createElement("div");
domContainer.id = id;
document.body.append(domContainer);
}
const currentOptions = itemFromSingleOrMultiple(options, index), dom = this.dom(), oldIndex = dom.findIndex((v) => v.id === id);
if (oldIndex >= 0) {
const old = this.domItem(oldIndex);
if (old && !old.destroyed) {
old.destroy();
dom.splice(oldIndex, 1);
}
}
let canvasEl;
if (domContainer.tagName.toLowerCase() === "canvas") {
canvasEl = domContainer;
canvasEl.dataset[generatedAttribute] = "false";
}
else {
const existingCanvases = domContainer.getElementsByTagName("canvas");
if (existingCanvases.length) {
canvasEl = existingCanvases[0];
canvasEl.dataset[generatedAttribute] = "false";
}
else {
canvasEl = document.createElement("canvas");
canvasEl.dataset[generatedAttribute] = "true";
domContainer.appendChild(canvasEl);
}
}
if (!canvasEl.style.width) {
canvasEl.style.width = "100%";
}
if (!canvasEl.style.height) {
canvasEl.style.height = "100%";
}
const newItem = new Container(this, id, currentOptions);
if (oldIndex >= 0) {
dom.splice(oldIndex, 0, newItem);
}
else {
dom.push(newItem);
}
newItem.canvas.loadCanvas(canvasEl);
await newItem.start();
return newItem;
}
}