@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.
341 lines (340 loc) • 11.7 kB
JavaScript
import { cloneStyle, getFullScreenStyle, safeMatchMedia, safeMutationObserver } from "../Utils/Utils.js";
import { defaultZoom, generatedAttribute, half } from "./Utils/Constants.js";
import { getStyleFromRgb, rangeColorToRgb } from "../Utils/ColorUtils.js";
import { RenderManager } from "./RenderManager.js";
const transferredCanvases = new WeakMap(), getTransferredCanvas = (canvas) => {
const transferredCanvas = transferredCanvases.get(canvas);
if (transferredCanvas) {
return transferredCanvas;
}
if (typeof canvas.transferControlToOffscreen !== "function") {
throw new TypeError("OffscreenCanvas is required but not supported by this browser");
}
try {
const offscreenCanvas = canvas.transferControlToOffscreen();
transferredCanvases.set(canvas, offscreenCanvas);
return offscreenCanvas;
}
catch {
throw new TypeError("OffscreenCanvas transfer failed");
}
}, isHtmlCanvasElement = (canvas) => {
return typeof HTMLCanvasElement !== "undefined" && canvas instanceof HTMLCanvasElement;
};
function setStyle(canvas, style, important = false) {
if (!style) {
return;
}
const element = canvas, elementStyle = element.style, keys = new Set();
for (let i = 0; i < elementStyle.length; i++) {
const key = elementStyle.item(i);
if (!key) {
continue;
}
keys.add(key);
}
for (let i = 0; i < style.length; i++) {
const key = style.item(i);
if (!key) {
continue;
}
keys.add(key);
}
for (const key of keys) {
const value = style.getPropertyValue(key);
if (value) {
elementStyle.setProperty(key, value, important ? "important" : "");
}
else {
elementStyle.removeProperty(key);
}
}
}
export class CanvasManager {
domElement;
render;
renderCanvas;
size;
zoom = defaultZoom;
#container;
#generated;
#mutationObserver;
#originalStyle;
#pluginManager;
#pointerEvents;
#resizePlugins;
#standardSize;
#zoomCenter;
constructor(pluginManager, container) {
this.#pluginManager = pluginManager;
this.#container = container;
this.render = new RenderManager(pluginManager, container, this);
this.#standardSize = {
height: 0,
width: 0,
};
const pxRatio = container.retina.pixelRatio, stdSize = this.#standardSize;
this.size = {
height: stdSize.height * pxRatio,
width: stdSize.width * pxRatio,
};
this.#generated = false;
this.#resizePlugins = [];
this.#pointerEvents = "none";
}
get #fullScreen() {
return this.#container.actualOptions.fullScreen.enable;
}
destroy() {
this.stop();
if (this.#generated) {
const element = this.domElement;
element?.remove();
this.domElement = undefined;
this.renderCanvas = undefined;
}
else {
this.#resetOriginalStyle();
}
this.render.destroy();
this.#resizePlugins = [];
}
getZoomCenter() {
const pxRatio = this.#container.retina.pixelRatio, { width, height } = this.size;
if (this.#zoomCenter) {
return this.#zoomCenter;
}
return {
x: (width * half) / pxRatio,
y: (height * half) / pxRatio,
};
}
init() {
this.#safeMutationObserver(obs => {
obs.disconnect();
});
this.#mutationObserver = safeMutationObserver(records => {
for (const record of records) {
if (record.type === "attributes" && record.attributeName === "style") {
this.#repairStyle();
}
}
});
this.resize();
this.#initStyle();
this.initBackground();
this.#safeMutationObserver(obs => {
const element = this.domElement;
if (!element || !(element instanceof Node)) {
return;
}
obs.observe(element, { attributes: true });
});
this.initPlugins();
this.render.init();
}
initBackground() {
const container = this.#container, options = container.actualOptions, background = options.background, element = this.domElement;
if (!element) {
return;
}
const elementStyle = element.style, color = rangeColorToRgb(this.#pluginManager, background.color);
if (color) {
elementStyle.backgroundColor = getStyleFromRgb(color, container.hdr, background.opacity);
}
else {
elementStyle.backgroundColor = "";
}
elementStyle.backgroundImage = background.image || "";
elementStyle.backgroundPosition = background.position || "";
elementStyle.backgroundRepeat = background.repeat || "";
elementStyle.backgroundSize = background.size || "";
}
initPlugins() {
this.#resizePlugins = [];
for (const plugin of this.#container.plugins) {
if (plugin.resize) {
this.#resizePlugins.push(plugin);
}
}
}
loadCanvas(canvas) {
if (this.#generated && this.domElement) {
this.domElement.remove();
}
const container = this.#container, domCanvas = isHtmlCanvasElement(canvas) ? canvas : undefined;
this.domElement = domCanvas;
this.#generated = domCanvas ? domCanvas.dataset[generatedAttribute] === "true" : false;
this.renderCanvas = domCanvas ? getTransferredCanvas(domCanvas) : canvas;
const domElement = this.domElement;
if (domElement) {
domElement.ariaHidden = "true";
this.#originalStyle = cloneStyle(domElement.style);
}
const standardSize = this.#standardSize, renderCanvas = this.renderCanvas;
if (domElement) {
standardSize.height = domElement.offsetHeight;
standardSize.width = domElement.offsetWidth;
}
else {
standardSize.height = renderCanvas.height;
standardSize.width = renderCanvas.width;
}
const pxRatio = this.#container.retina.pixelRatio, retinaSize = this.size;
renderCanvas.height = retinaSize.height = standardSize.height * pxRatio;
renderCanvas.width = retinaSize.width = standardSize.width * pxRatio;
const canSupportHdrQuery = safeMatchMedia("(color-gamut: p3)");
this.render.setContextSettings({
alpha: true,
colorSpace: canSupportHdrQuery?.matches && container.hdr ? "display-p3" : "srgb",
desynchronized: true,
willReadFrequently: false,
});
this.render.setContext(renderCanvas.getContext("2d", this.render.settings));
this.#safeMutationObserver(obs => {
obs.disconnect();
});
container.retina.init();
this.initBackground();
this.#safeMutationObserver(obs => {
const element = this.domElement;
if (!element || !(element instanceof Node)) {
return;
}
obs.observe(element, { attributes: true });
});
}
resize() {
const element = this.domElement;
if (!element) {
return false;
}
const container = this.#container, renderCanvas = this.renderCanvas;
if (renderCanvas === undefined) {
return false;
}
const currentSize = container.canvas.#standardSize, newSize = {
width: element.offsetWidth,
height: element.offsetHeight,
}, pxRatio = container.retina.pixelRatio, retinaSize = {
width: newSize.width * pxRatio,
height: newSize.height * pxRatio,
};
if (newSize.height === currentSize.height &&
newSize.width === currentSize.width &&
retinaSize.height === renderCanvas.height &&
retinaSize.width === renderCanvas.width) {
return false;
}
const oldSize = { ...currentSize };
currentSize.height = newSize.height;
currentSize.width = newSize.width;
const canvasSize = this.size;
renderCanvas.width = canvasSize.width = retinaSize.width;
renderCanvas.height = canvasSize.height = retinaSize.height;
if (this.#container.started) {
container.particles.setResizeFactor({
width: currentSize.width / oldSize.width,
height: currentSize.height / oldSize.height,
});
}
return true;
}
setPointerEvents(type) {
const element = this.domElement;
if (!element) {
return;
}
this.#pointerEvents = type;
this.#repairStyle();
}
setZoom(zoomLevel, center) {
this.zoom = zoomLevel;
this.#zoomCenter = center;
}
stop() {
this.#safeMutationObserver(obs => {
obs.disconnect();
});
this.#mutationObserver = undefined;
this.render.stop();
}
async windowResize() {
if (!this.domElement || !this.resize()) {
return;
}
const container = this.#container, needsRefresh = container.updateActualOptions();
container.particles.setDensity();
this.#applyResizePlugins();
if (needsRefresh) {
await container.refresh();
}
}
#applyResizePlugins = () => {
for (const plugin of this.#resizePlugins) {
plugin.resize?.();
}
};
#initStyle = () => {
const element = this.domElement, options = this.#container.actualOptions;
if (!element) {
return;
}
if (this.#fullScreen) {
this.#setFullScreenStyle();
}
else {
this.#resetOriginalStyle();
}
for (const key in options.style) {
if (!key || !(key in options.style)) {
continue;
}
const value = options.style[key];
if (!value) {
continue;
}
element.style.setProperty(key, value, "important");
}
};
#repairStyle = () => {
const element = this.domElement;
if (!element) {
return;
}
this.#safeMutationObserver(observer => {
observer.disconnect();
});
this.#initStyle();
this.initBackground();
const pointerEvents = this.#pointerEvents;
element.style.pointerEvents = pointerEvents;
element.style.setProperty("pointer-events", pointerEvents);
this.#safeMutationObserver(observer => {
if (!(element instanceof Node)) {
return;
}
observer.observe(element, { attributes: true });
});
};
#resetOriginalStyle = () => {
const element = this.domElement, originalStyle = this.#originalStyle;
if (!element || !originalStyle) {
return;
}
setStyle(element, originalStyle, true);
};
#safeMutationObserver = callback => {
if (!this.#mutationObserver) {
return;
}
callback(this.#mutationObserver);
};
#setFullScreenStyle = () => {
const element = this.domElement;
if (!element) {
return;
}
setStyle(element, getFullScreenStyle(this.#container.actualOptions.fullScreen.zIndex), true);
};
}