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.

340 lines (339 loc) 11.6 kB
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.#initContext(); 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.actualOptions.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 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; } 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?.(); } } #initContext() { const container = this.#container, canSupportHdr = container.actualOptions.hdr && safeMatchMedia("(color-gamut: p3)")?.matches && safeMatchMedia("(dynamic-range: high)")?.matches; this.render.setContextSettings({ alpha: true, desynchronized: true, willReadFrequently: false, ...(canSupportHdr ? { colorSpace: "display-p3", colorType: "float16" } : { colorSpace: "srgb" }), }); const renderCanvas = this.renderCanvas; if (!renderCanvas) { return; } this.render.setContext(renderCanvas.getContext("2d", this.render.settings)); } #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); } }