UNPKG

@tsparticles/plugin-polygon-mask

Version:

tsParticles polygon mask plugin

395 lines (394 loc) 17.6 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.PolygonMaskInstance = void 0; const engine_1 = require("@tsparticles/engine"); const utils_js_1 = require("./utils.js"); const PolygonMaskInlineArrangement_js_1 = require("./Enums/PolygonMaskInlineArrangement.js"); const PolygonMaskType_js_1 = require("./Enums/PolygonMaskType.js"); const noPolygonDataLoaded = `${engine_1.errorPrefix} No polygon data loaded.`, noPolygonFound = `${engine_1.errorPrefix} No polygon found, you need to specify SVG url in config.`, origin = { x: 0, y: 0, }, half = 0.5, double = 2; class PolygonMaskInstance { constructor(container, engine) { this._checkInsidePolygon = position => { const container = this._container, options = container.actualOptions.polygon; if (!options?.enable || options.type === PolygonMaskType_js_1.PolygonMaskType.none || options.type === PolygonMaskType_js_1.PolygonMaskType.inline) { return true; } if (!this.raw) { throw new Error(noPolygonFound); } const canvasSize = container.canvas.size, x = position?.x ?? (0, engine_1.getRandom)() * canvasSize.width, y = position?.y ?? (0, engine_1.getRandom)() * canvasSize.height, indexOffset = 1; let inside = false; for (let i = 0, j = this.raw.length - indexOffset; i < this.raw.length; j = i++) { const pi = this.raw[i], pj = this.raw[j], intersect = pi.y > y !== pj.y > y && x < ((pj.x - pi.x) * (y - pi.y)) / (pj.y - pi.y) + pi.x; if (intersect) { inside = !inside; } } if (options.type === PolygonMaskType_js_1.PolygonMaskType.inside) { return inside; } else { return options.type === PolygonMaskType_js_1.PolygonMaskType.outside ? !inside : false; } }; this._createPath2D = () => { const container = this._container, options = container.actualOptions.polygon; if (!options || !this.paths?.length) { return; } for (const path of this.paths) { const pathData = path.element?.getAttribute("d"); if (pathData) { const path2d = new Path2D(pathData), matrix = document.createElementNS("http://www.w3.org/2000/svg", "svg").createSVGMatrix(), finalPath = new Path2D(), transform = matrix.scale(this._scale); if (finalPath.addPath) { finalPath.addPath(path2d, transform); path.path2d = finalPath; } else { delete path.path2d; } } else { delete path.path2d; } if (path.path2d ?? !this.raw) { continue; } path.path2d = new Path2D(); const firstIndex = 0, firstPoint = this.raw[firstIndex]; path.path2d.moveTo(firstPoint.x, firstPoint.y); this.raw.forEach((pos, i) => { if (i > firstIndex) { path.path2d?.lineTo(pos.x, pos.y); } }); path.path2d.closePath(); } }; this._downloadSvgPath = async (svgUrl, force) => { const options = this._container.actualOptions.polygon; if (!options) { return; } const url = svgUrl ?? options.url, forceDownload = force ?? false; if (!url || (this.paths !== undefined && !forceDownload)) { return this.raw; } const req = await fetch(url); if (!req.ok) { throw new Error(`${engine_1.errorPrefix} occurred during polygon mask download`); } return this._parseSvgPath(await req.text(), force); }; this._drawPoints = () => { if (!this.raw) { return; } for (const item of this.raw) { void this._container.particles.addParticle({ x: item.x, y: item.y, }); } }; this._getEquidistantPointByIndex = index => { const container = this._container, options = container.actualOptions, polygonMaskOptions = options.polygon; if (!polygonMaskOptions) { return; } if (!this.raw?.length || !this.paths?.length) { throw new Error(noPolygonDataLoaded); } let offset = 0, point; const baseAccumulator = 0, totalLength = this.paths.reduce((tot, path) => tot + path.length, baseAccumulator), distance = totalLength / options.particles.number.value; for (const path of this.paths) { const pathDistance = distance * index - offset; if (pathDistance <= path.length) { point = path.element.getPointAtLength(pathDistance); break; } else { offset += path.length; } } const scale = this._scale; return { x: (point?.x ?? origin.x) * scale + (this.offset?.x ?? origin.x), y: (point?.y ?? origin.y) * scale + (this.offset?.y ?? origin.y), }; }; this._getPointByIndex = index => { if (!this.raw?.length) { throw new Error(noPolygonDataLoaded); } const coords = this.raw[index % this.raw.length]; return { x: coords.x, y: coords.y, }; }; this._getRandomPoint = () => { if (!this.raw?.length) { throw new Error(noPolygonDataLoaded); } const coords = (0, engine_1.itemFromArray)(this.raw); return { x: coords.x, y: coords.y, }; }; this._getRandomPointByLength = () => { const container = this._container, options = container.actualOptions.polygon; if (!options) { return; } if (!this.raw?.length || !this.paths?.length) { throw new Error(noPolygonDataLoaded); } const path = (0, engine_1.itemFromArray)(this.paths), offset = 1, distance = Math.floor((0, engine_1.getRandom)() * path.length) + offset, point = path.element.getPointAtLength(distance), scale = this._scale; return { x: point.x * scale + (this.offset?.x ?? origin.x), y: point.y * scale + (this.offset?.y ?? origin.y), }; }; this._initRawData = async (force) => { const options = this._container.actualOptions.polygon; if (!options) { return; } if (options.url) { this.raw = await this._downloadSvgPath(options.url, force); } else if (options.data) { const data = options.data; let svg; if ((0, engine_1.isString)(data)) { svg = data; } else { const getPath = (p) => `<path d="${p}" />`, path = (0, engine_1.isArray)(data.path) ? data.path.map(getPath).join("") : getPath(data.path); const namespaces = 'xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"'; svg = `<svg ${namespaces} width="${data.size.width}" height="${data.size.height}">${path}</svg>`; } this.raw = this._parseSvgPath(svg, force); } this._createPath2D(); this._engine.dispatchEvent("polygonMaskLoaded", { container: this._container, }); }; this._parseSvgPath = (xml, force) => { const forceDownload = force ?? false; if (this.paths !== undefined && !forceDownload) { return this.raw; } const container = this._container, options = container.actualOptions.polygon; if (!options) { return; } const parser = new DOMParser(), doc = parser.parseFromString(xml, "image/svg+xml"), firstIndex = 0, svg = doc.getElementsByTagName("svg")[firstIndex]; let svgPaths = svg.getElementsByTagName("path"); if (!svgPaths.length) { svgPaths = doc.getElementsByTagName("path"); } this.paths = []; for (let i = 0; i < svgPaths.length; i++) { const path = svgPaths.item(i); if (path) { this.paths.push({ element: path, length: path.getTotalLength(), }); } } const scale = this._scale; this.dimension.width = parseFloat(svg.getAttribute("width") ?? "0") * scale; this.dimension.height = parseFloat(svg.getAttribute("height") ?? "0") * scale; const position = options.position ?? { x: 50, y: 50, }, canvasSize = container.canvas.size; this.offset = { x: (canvasSize.width * position.x) / engine_1.percentDenominator - this.dimension.width * half, y: (canvasSize.height * position.y) / engine_1.percentDenominator - this.dimension.height * half, }; return (0, utils_js_1.parsePaths)(this.paths, scale, this.offset); }; this._polygonBounce = (particle, delta, direction) => { const options = this._container.actualOptions.polygon; if (!this.raw || !options?.enable || direction !== engine_1.OutModeDirection.top) { return false; } if (options.type === PolygonMaskType_js_1.PolygonMaskType.inside || options.type === PolygonMaskType_js_1.PolygonMaskType.outside) { let closest, dx, dy; const pos = particle.getPosition(), radius = particle.getRadius(), offset = 1; for (let i = 0, j = this.raw.length - offset; i < this.raw.length; j = i++) { const pi = this.raw[i], pj = this.raw[j]; closest = (0, utils_js_1.calcClosestPointOnSegment)(pi, pj, pos); const dist = (0, engine_1.getDistances)(pos, closest); [dx, dy] = [dist.dx, dist.dy]; if (dist.distance < radius) { (0, utils_js_1.segmentBounce)(pi, pj, particle.velocity); return true; } } if (closest && dx !== undefined && dy !== undefined && !this._checkInsidePolygon(pos)) { const factor = { x: 1, y: 1 }, diameter = radius * double, inverse = -1; if (pos.x >= closest.x) { factor.x = -1; } if (pos.y >= closest.y) { factor.y = -1; } particle.position.x = closest.x + diameter * factor.x; particle.position.y = closest.y + diameter * factor.y; particle.velocity.mult(inverse); return true; } } else if (options.type === PolygonMaskType_js_1.PolygonMaskType.inline && particle.initialPosition) { const dist = (0, engine_1.getDistance)(particle.initialPosition, particle.getPosition()), { velocity } = particle; if (dist > this._moveRadius) { velocity.x = velocity.y * half - velocity.x; velocity.y = velocity.x * half - velocity.y; return true; } } return false; }; this._randomPoint = () => { const container = this._container, options = container.actualOptions.polygon; if (!options) { return; } let position; if (options.type === PolygonMaskType_js_1.PolygonMaskType.inline) { switch (options.inline.arrangement) { case PolygonMaskInlineArrangement_js_1.PolygonMaskInlineArrangement.randomPoint: position = this._getRandomPoint(); break; case PolygonMaskInlineArrangement_js_1.PolygonMaskInlineArrangement.randomLength: position = this._getRandomPointByLength(); break; case PolygonMaskInlineArrangement_js_1.PolygonMaskInlineArrangement.equidistant: position = this._getEquidistantPointByIndex(container.particles.count); break; case PolygonMaskInlineArrangement_js_1.PolygonMaskInlineArrangement.onePerPoint: case PolygonMaskInlineArrangement_js_1.PolygonMaskInlineArrangement.perPoint: default: position = this._getPointByIndex(container.particles.count); } } else { const canvasSize = container.canvas.size; position = { x: (0, engine_1.getRandom)() * canvasSize.width, y: (0, engine_1.getRandom)() * canvasSize.height, }; } if (this._checkInsidePolygon(position)) { return position; } else { return this._randomPoint(); } }; this._container = container; this._engine = engine; this.dimension = { height: 0, width: 0, }; this._moveRadius = 0; this._scale = 1; } clickPositionValid(position) { const options = this._container.actualOptions.polygon; return (!!options?.enable && options.type !== PolygonMaskType_js_1.PolygonMaskType.none && options.type !== PolygonMaskType_js_1.PolygonMaskType.inline && this._checkInsidePolygon(position)); } draw(context) { if (!this.paths?.length) { return; } const options = this._container.actualOptions.polygon; if (!options?.enable) { return; } const polygonDraw = options.draw; if (!polygonDraw.enable) { return; } const rawData = this.raw; for (const path of this.paths) { const path2d = path.path2d; if (!context) { continue; } if (path2d && this.offset) { (0, utils_js_1.drawPolygonMaskPath)(this._engine, context, path2d, polygonDraw.stroke, this.offset); } else if (rawData) { (0, utils_js_1.drawPolygonMask)(this._engine, context, rawData, polygonDraw.stroke); } } } async init() { const container = this._container, polygonMaskOptions = container.actualOptions.polygon, pxRatio = container.retina.pixelRatio; if (!polygonMaskOptions) { return; } this._moveRadius = polygonMaskOptions.move.radius * pxRatio; this._scale = polygonMaskOptions.scale * pxRatio; if (polygonMaskOptions.enable) { await this._initRawData(); } } particleBounce(particle, delta, direction) { return this._polygonBounce(particle, delta, direction); } particlePosition(position) { const options = this._container.actualOptions.polygon, defaultLength = 0; if (!(options?.enable && (this.raw?.length ?? defaultLength) > defaultLength)) { return; } return (0, engine_1.deepExtend)({}, position ? position : this._randomPoint()); } particlesInitialization() { const options = this._container.actualOptions.polygon; if (options?.enable && options.type === PolygonMaskType_js_1.PolygonMaskType.inline && (options.inline.arrangement === PolygonMaskInlineArrangement_js_1.PolygonMaskInlineArrangement.onePerPoint || options.inline.arrangement === PolygonMaskInlineArrangement_js_1.PolygonMaskInlineArrangement.perPoint)) { this._drawPoints(); return true; } return false; } resize() { const container = this._container, options = container.actualOptions.polygon; if (!(options?.enable && options.type !== PolygonMaskType_js_1.PolygonMaskType.none)) { return; } if (this.redrawTimeout) { clearTimeout(this.redrawTimeout); } const timeout = 250; this.redrawTimeout = setTimeout(() => { void (async () => { await this._initRawData(true); await container.particles.redraw(); })(); }, timeout); } stop() { delete this.raw; delete this.paths; } } exports.PolygonMaskInstance = PolygonMaskInstance;