@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.
304 lines (303 loc) • 10.8 kB
JavaScript
import { clear, drawParticle, drawParticlePlugin, paintBase, paintImage } from "../Utils/CanvasUtils.js";
import { defaultCompositeValue, defaultTransformValue, minimumSize, zIndexFactorOffset } from "./Utils/Constants.js";
import { getStyleFromHsl, rangeColorToHsl } from "../Utils/ColorUtils.js";
const fColorIndex = 0, sColorIndex = 1;
function setTransformValue(factor, newFactor, key) {
const newValue = newFactor[key];
if (newValue !== undefined) {
factor[key] = (factor[key] ?? defaultTransformValue) * newValue;
}
}
export class RenderManager {
#canvasClearPlugins;
#canvasManager;
#canvasPaintPlugins;
#clearDrawPlugins;
#colorPlugins;
#container;
#context;
#contextSettings;
#drawParticlePlugins;
#drawParticlesCleanupPlugins;
#drawParticlesSetupPlugins;
#drawPlugins;
#drawSettingsCleanupPlugins;
#drawSettingsSetupPlugins;
#pluginManager;
#postDrawUpdaters;
#preDrawUpdaters;
#reusableColorStyles = {};
#reusablePluginColors = [undefined, undefined];
#reusableTransform = {};
constructor(pluginManager, container, canvasManager) {
this.#pluginManager = pluginManager;
this.#container = container;
this.#canvasManager = canvasManager;
this.#context = null;
this.#preDrawUpdaters = [];
this.#postDrawUpdaters = [];
this.#colorPlugins = [];
this.#canvasClearPlugins = [];
this.#canvasPaintPlugins = [];
this.#clearDrawPlugins = [];
this.#drawParticlePlugins = [];
this.#drawParticlesCleanupPlugins = [];
this.#drawParticlesSetupPlugins = [];
this.#drawPlugins = [];
this.#drawSettingsSetupPlugins = [];
this.#drawSettingsCleanupPlugins = [];
}
get settings() {
return this.#contextSettings;
}
canvasClear() {
if (!this.#container.actualOptions.clear) {
return;
}
this.draw(ctx => {
clear(ctx, this.#canvasManager.size);
});
}
clear() {
let pluginHandled = false;
for (const plugin of this.#canvasClearPlugins) {
pluginHandled = plugin.canvasClear?.() ?? false;
if (pluginHandled) {
break;
}
}
if (pluginHandled) {
return;
}
this.canvasClear();
}
destroy() {
this.stop();
this.#preDrawUpdaters = [];
this.#postDrawUpdaters = [];
this.#colorPlugins = [];
this.#canvasClearPlugins = [];
this.#canvasPaintPlugins = [];
this.#clearDrawPlugins = [];
this.#drawParticlePlugins = [];
this.#drawParticlesCleanupPlugins = [];
this.#drawParticlesSetupPlugins = [];
this.#drawPlugins = [];
this.#drawSettingsSetupPlugins = [];
this.#drawSettingsCleanupPlugins = [];
}
draw(cb) {
const ctx = this.#context;
if (!ctx) {
return;
}
return cb(ctx);
}
drawParticle(particle, delta) {
if (particle.spawning || particle.destroyed) {
return;
}
const radius = particle.getRadius();
if (radius <= minimumSize) {
return;
}
const pfColor = particle.getFillColor(), psColor = particle.getStrokeColor();
let [fColor, sColor] = this.#getPluginParticleColors(particle);
fColor ??= pfColor;
sColor ??= psColor;
if (!fColor && !sColor) {
return;
}
const container = this.#container, zIndexOptions = particle.options.zIndex, zIndexFactor = zIndexFactorOffset - particle.zIndexFactor, { fillOpacity, opacity, strokeOpacity } = particle.getOpacity(), transform = this.#reusableTransform, colorStyles = this.#reusableColorStyles, fill = fColor ? getStyleFromHsl(fColor, container.hdr, fillOpacity * opacity) : undefined, stroke = sColor ? getStyleFromHsl(sColor, container.hdr, strokeOpacity * opacity) : fill;
transform.a = transform.b = transform.c = transform.d = undefined;
colorStyles.fill = fill;
colorStyles.stroke = stroke;
this.draw((context) => {
for (const plugin of this.#drawParticlesSetupPlugins) {
plugin.drawParticleSetup?.(context, particle, delta);
}
this.#applyPreDrawUpdaters(context, particle, radius, opacity, colorStyles, transform);
drawParticle({
container,
context,
particle,
delta,
colorStyles,
radius: radius * zIndexFactor ** zIndexOptions.sizeRate,
opacity: opacity,
transform,
});
this.#applyPostDrawUpdaters(particle);
for (const plugin of this.#drawParticlesCleanupPlugins) {
plugin.drawParticleCleanup?.(context, particle, delta);
}
});
}
drawParticlePlugins(particle, delta) {
this.draw(ctx => {
for (const plugin of this.#drawParticlePlugins) {
drawParticlePlugin(ctx, plugin, particle, delta);
}
});
}
drawParticles(delta) {
const { particles } = this.#container;
this.clear();
particles.update(delta);
this.draw(ctx => {
for (const plugin of this.#drawSettingsSetupPlugins) {
plugin.drawSettingsSetup?.(ctx, delta);
}
for (const plugin of this.#drawPlugins) {
plugin.draw?.(ctx, delta);
}
particles.drawParticles(delta);
for (const plugin of this.#clearDrawPlugins) {
plugin.clearDraw?.(ctx, delta);
}
for (const plugin of this.#drawSettingsCleanupPlugins) {
plugin.drawSettingsCleanup?.(ctx, delta);
}
});
}
init() {
this.initUpdaters();
this.initPlugins();
this.paint();
}
initPlugins() {
this.#colorPlugins = [];
this.#canvasClearPlugins = [];
this.#canvasPaintPlugins = [];
this.#clearDrawPlugins = [];
this.#drawParticlePlugins = [];
this.#drawParticlesSetupPlugins = [];
this.#drawParticlesCleanupPlugins = [];
this.#drawPlugins = [];
this.#drawSettingsSetupPlugins = [];
this.#drawSettingsCleanupPlugins = [];
for (const plugin of this.#container.plugins) {
if (plugin.particleFillColor ?? plugin.particleStrokeColor) {
this.#colorPlugins.push(plugin);
}
if (plugin.canvasClear) {
this.#canvasClearPlugins.push(plugin);
}
if (plugin.canvasPaint) {
this.#canvasPaintPlugins.push(plugin);
}
if (plugin.drawParticle) {
this.#drawParticlePlugins.push(plugin);
}
if (plugin.drawParticleSetup) {
this.#drawParticlesSetupPlugins.push(plugin);
}
if (plugin.drawParticleCleanup) {
this.#drawParticlesCleanupPlugins.push(plugin);
}
if (plugin.draw) {
this.#drawPlugins.push(plugin);
}
if (plugin.drawSettingsSetup) {
this.#drawSettingsSetupPlugins.push(plugin);
}
if (plugin.drawSettingsCleanup) {
this.#drawSettingsCleanupPlugins.push(plugin);
}
if (plugin.clearDraw) {
this.#clearDrawPlugins.push(plugin);
}
}
}
initUpdaters() {
this.#preDrawUpdaters = [];
this.#postDrawUpdaters = [];
for (const updater of this.#container.particleUpdaters) {
if (updater.afterDraw) {
this.#postDrawUpdaters.push(updater);
}
if (updater.getColorStyles ?? updater.getTransformValues ?? updater.beforeDraw) {
this.#preDrawUpdaters.push(updater);
}
}
}
paint() {
let handled = false;
for (const plugin of this.#canvasPaintPlugins) {
handled = plugin.canvasPaint?.() ?? false;
if (handled) {
break;
}
}
if (handled) {
return;
}
this.paintBase();
}
paintBase(baseColor) {
this.draw(ctx => {
paintBase(ctx, this.#canvasManager.size, baseColor);
});
}
paintImage(image, opacity) {
this.draw(ctx => {
paintImage(ctx, this.#canvasManager.size, image, opacity);
});
}
setContext(context) {
this.#context = context;
if (this.#context) {
this.#context.globalCompositeOperation = defaultCompositeValue;
}
}
setContextSettings(settings) {
this.#contextSettings = settings;
}
stop() {
this.draw(ctx => {
clear(ctx, this.#canvasManager.size);
});
}
#applyPostDrawUpdaters = particle => {
for (const updater of this.#postDrawUpdaters) {
updater.afterDraw?.(particle);
}
};
#applyPreDrawUpdaters = (ctx, particle, radius, zOpacity, colorStyles, transform) => {
for (const updater of this.#preDrawUpdaters) {
if (updater.getColorStyles) {
const { fill, stroke } = updater.getColorStyles(particle, ctx, radius, zOpacity);
if (fill) {
colorStyles.fill = fill;
}
if (stroke) {
colorStyles.stroke = stroke;
}
}
if (updater.getTransformValues) {
const updaterTransform = updater.getTransformValues(particle);
for (const key in updaterTransform) {
setTransformValue(transform, updaterTransform, key);
}
}
updater.beforeDraw?.(particle);
}
};
#getPluginParticleColors = particle => {
let fColor, sColor;
for (const plugin of this.#colorPlugins) {
if (!fColor && plugin.particleFillColor) {
fColor = rangeColorToHsl(this.#pluginManager, plugin.particleFillColor(particle));
}
if (!sColor && plugin.particleStrokeColor) {
sColor = rangeColorToHsl(this.#pluginManager, plugin.particleStrokeColor(particle));
}
if (fColor && sColor) {
break;
}
}
this.#reusablePluginColors[fColorIndex] = fColor;
this.#reusablePluginColors[sColorIndex] = sColor;
return this.#reusablePluginColors;
};
}