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.

237 lines (236 loc) 8.76 kB
import { collisionVelocity, getDistances, getRandom, getRangeValue } from "./MathUtils.js"; import { isArray, isBoolean, isNull, isObject } from "./TypeUtils.js"; import { OutModeDirection } from "../Enums/Directions/OutModeDirection.js"; import { PixelMode } from "../Enums/Modes/PixelMode.js"; import { Vector } from "../Core/Utils/Vectors.js"; import { percentDenominator } from "../Core/Utils/Constants.js"; const minRadius = 0; function hasMatchMedia() { return typeof matchMedia !== "undefined"; } export function safeDocument() { return globalThis.document; } export function safeMatchMedia(query) { if (!hasMatchMedia()) { return; } return matchMedia(query); } export function safeMutationObserver(callback) { if (typeof MutationObserver === "undefined") { return; } return new MutationObserver(callback); } export function isInArray(value, array) { return value === array || (isArray(array) && array.includes(value)); } export function itemFromArray(array, index, useIndex = true) { return array[index !== undefined && useIndex ? index % array.length : Math.floor(getRandom() * array.length)]; } export function isPointInside(point, size, offset, radius, direction) { return areBoundsInside(calculateBounds(point, radius ?? minRadius), size, offset, direction); } export function areBoundsInside(bounds, size, offset, direction) { let inside = true; if (!direction || direction === OutModeDirection.bottom) { inside = bounds.top < size.height + offset.x; } if (inside && (!direction || direction === OutModeDirection.left)) { inside = bounds.right > offset.x; } if (inside && (!direction || direction === OutModeDirection.right)) { inside = bounds.left < size.width + offset.y; } if (inside && (!direction || direction === OutModeDirection.top)) { inside = bounds.bottom > offset.y; } return inside; } export function calculateBounds(point, radius) { return { bottom: point.y + radius, left: point.x - radius, right: point.x + radius, top: point.y - radius, }; } export function deepExtend(destination, ...sources) { for (const source of sources) { if (isNull(source)) { continue; } if (!isObject(source)) { destination = source; continue; } if (Array.isArray(source)) { if (!Array.isArray(destination)) { destination = []; } } else if (!isObject(destination) || Array.isArray(destination)) { destination = Object.create(null); } const sourceKeys = Object.keys(source), hasNested = sourceKeys.some(k => { const v = source[k]; return isObject(v) || Array.isArray(v); }); if (!hasNested) { const sourceDict = source, destDict = destination; for (const key of sourceKeys) { if (key === "__proto__" || key === "constructor" || key === "prototype") { continue; } const v = sourceDict[key]; if (v !== undefined) { destDict[key] = v; } } continue; } for (const key of sourceKeys) { if (key === "__proto__" || key === "constructor" || key === "prototype") { continue; } const sourceDict = source, destDict = destination, value = sourceDict[key]; destDict[key] = Array.isArray(value) ? value.map(v => deepExtend(undefined, v)) : deepExtend(destDict[key], value); } } return destination; } export function circleBounceDataFromParticle(p) { return { position: p.getPosition(), radius: p.getRadius(), mass: p.getMass(), velocity: p.velocity, factor: Vector.create(getRangeValue(p.options.bounce.horizontal.value), getRangeValue(p.options.bounce.vertical.value)), }; } export function circleBounce(p1, p2) { const { x: xVelocityDiff, y: yVelocityDiff } = p1.velocity.sub(p2.velocity), [pos1, pos2] = [p1.position, p2.position], { dx: xDist, dy: yDist } = getDistances(pos2, pos1), minimumDistance = 0; if (xVelocityDiff * xDist + yVelocityDiff * yDist < minimumDistance) { return; } const angle = -Math.atan2(yDist, xDist), m1 = p1.mass, m2 = p2.mass, u1 = p1.velocity.rotate(angle), u2 = p2.velocity.rotate(angle), v1 = collisionVelocity(u1, u2, m1, m2), v2 = collisionVelocity(u2, u1, m1, m2), vFinal1 = v1.rotate(-angle), vFinal2 = v2.rotate(-angle); p1.velocity.x = vFinal1.x * p1.factor.x; p1.velocity.y = vFinal1.y * p1.factor.y; p2.velocity.x = vFinal2.x * p2.factor.x; p2.velocity.y = vFinal2.y * p2.factor.y; } export function executeOnSingleOrMultiple(obj, callback) { const defaultIndex = 0; return isArray(obj) ? obj.map((item, index) => callback(item, index)) : callback(obj, defaultIndex); } export function itemFromSingleOrMultiple(obj, index, useIndex) { return isArray(obj) ? itemFromArray(obj, index, useIndex) : obj; } function getPositionOrSize(positionOrSize, canvasSize) { const isPercent = positionOrSize.mode === PixelMode.percent; if (!isPercent) { const { mode: _, ...rest } = positionOrSize; return rest; } const isPosition = "x" in positionOrSize; if (isPosition) { return { x: (positionOrSize.x / percentDenominator) * canvasSize.width, y: (positionOrSize.y / percentDenominator) * canvasSize.height, }; } else { return { width: (positionOrSize.width / percentDenominator) * canvasSize.width, height: (positionOrSize.height / percentDenominator) * canvasSize.height, }; } } export function getPosition(position, canvasSize) { return getPositionOrSize(position, canvasSize); } export function cloneStyle(style) { const clonedStyle = safeDocument().createElement("div").style; for (const key in style) { const styleKey = style[key]; if (!(key in style) || isNull(styleKey)) { continue; } const styleValue = style.getPropertyValue?.(styleKey); if (!styleValue) { continue; } const stylePriority = style.getPropertyPriority?.(styleKey); if (stylePriority) { clonedStyle.setProperty(styleKey, styleValue, stylePriority); } else { clonedStyle.setProperty(styleKey, styleValue); } } return clonedStyle; } let _cachedZIndex, _cachedStyle; export function getFullScreenStyle(zIndex) { if (_cachedZIndex !== zIndex || !_cachedStyle) { _cachedZIndex = zIndex; const fullScreenStyle = safeDocument().createElement("div").style, radix = 10, style = { width: "100%", height: "100%", margin: "0", padding: "0", borderWidth: "0", position: "fixed", zIndex: zIndex.toString(radix), "z-index": zIndex.toString(radix), top: "0", left: "0", "pointer-events": "none", }; for (const key in style) { const value = style[key]; if (value === undefined) { continue; } fullScreenStyle.setProperty(key, value); } _cachedStyle = fullScreenStyle; } return _cachedStyle; } export function manageListener(element, event, handler, add, options) { if (add) { let addOptions = { passive: true }; if (isBoolean(options)) { addOptions.capture = options; } else if (options !== undefined) { addOptions = options; } element.addEventListener(event, handler, addOptions); } else { const removeOptions = options; element.removeEventListener(event, handler, removeOptions); } } export async function getItemsFromInitializer(container, map, initializers, force = false) { let res = map.get(container); if (!res || force) { res = await Promise.all([...initializers.values()].map(t => t(container))); map.set(container, res); } return res; } export async function getItemMapFromInitializer(container, map, initializers, force = false) { let res = map.get(container); if (!res || force) { const entries = await Promise.all([...initializers.entries()].map(([key, initializer]) => initializer(container).then(item => [key, item]))); res = new Map(entries); map.set(container, res); } return res; }