tsparticles
Version:
Easily create highly customizable particle 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.
472 lines (471 loc) • 20.8 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.Particle = void 0;
const Updater_1 = require("./Particle/Updater");
const ParticlesOptions_1 = require("../Options/Classes/Particles/ParticlesOptions");
const Shape_1 = require("../Options/Classes/Particles/Shape/Shape");
const Enums_1 = require("../Enums");
const Utils_1 = require("../Utils");
const Infecter_1 = require("./Particle/Infecter");
const Mover_1 = require("./Particle/Mover");
const Vector_1 = require("./Particle/Vector");
class Particle {
constructor(id, container, position, overrideOptions) {
var _a, _b, _c, _d, _e, _f, _g, _h, _j;
this.id = id;
this.container = container;
this.links = [];
this.fill = true;
this.close = true;
this.lastPathTime = 0;
this.destroyed = false;
this.unbreakable = false;
this.splitCount = 0;
this.misplaced = false;
this.loops = {
opacity: 0,
size: 0,
};
const pxRatio = container.retina.pixelRatio;
const options = container.actualOptions;
const particlesOptions = new ParticlesOptions_1.ParticlesOptions();
particlesOptions.load(options.particles);
const shapeType = particlesOptions.shape.type;
const reduceDuplicates = particlesOptions.reduceDuplicates;
this.shape = shapeType instanceof Array ? Utils_1.Utils.itemFromArray(shapeType, this.id, reduceDuplicates) : shapeType;
if (overrideOptions === null || overrideOptions === void 0 ? void 0 : overrideOptions.shape) {
if (overrideOptions.shape.type) {
const overrideShapeType = overrideOptions.shape.type;
this.shape =
overrideShapeType instanceof Array
? Utils_1.Utils.itemFromArray(overrideShapeType, this.id, reduceDuplicates)
: overrideShapeType;
}
const shapeOptions = new Shape_1.Shape();
shapeOptions.load(overrideOptions.shape);
if (this.shape) {
const shapeData = shapeOptions.options[this.shape];
if (shapeData) {
this.shapeData = Utils_1.Utils.deepExtend({}, shapeData instanceof Array
? Utils_1.Utils.itemFromArray(shapeData, this.id, reduceDuplicates)
: shapeData);
}
}
}
else {
const shapeData = particlesOptions.shape.options[this.shape];
if (shapeData) {
this.shapeData = Utils_1.Utils.deepExtend({}, shapeData instanceof Array ? Utils_1.Utils.itemFromArray(shapeData, this.id, reduceDuplicates) : shapeData);
}
}
if (overrideOptions !== undefined) {
particlesOptions.load(overrideOptions);
}
if (((_a = this.shapeData) === null || _a === void 0 ? void 0 : _a.particles) !== undefined) {
particlesOptions.load((_b = this.shapeData) === null || _b === void 0 ? void 0 : _b.particles);
}
this.fill = (_d = (_c = this.shapeData) === null || _c === void 0 ? void 0 : _c.fill) !== null && _d !== void 0 ? _d : this.fill;
this.close = (_f = (_e = this.shapeData) === null || _e === void 0 ? void 0 : _e.close) !== null && _f !== void 0 ? _f : this.close;
this.options = particlesOptions;
this.pathDelay = Utils_1.NumberUtils.getValue(this.options.move.path.delay) * 1000;
container.retina.initParticle(this);
const color = this.options.color;
const sizeOptions = this.options.size;
const sizeValue = Utils_1.NumberUtils.getValue(sizeOptions) * container.retina.pixelRatio;
const randomSize = typeof sizeOptions.random === "boolean" ? sizeOptions.random : sizeOptions.random.enable;
this.size = {
value: sizeValue,
};
this.direction = this.options.move.direction;
this.bubble = {
inRange: false,
};
this.initialVelocity = this.calculateVelocity();
this.velocity = this.initialVelocity.copy();
const rotateOptions = this.options.rotate;
this.rotate = {
value: (Utils_1.NumberUtils.getRangeValue(rotateOptions.value) * Math.PI) / 180,
};
let rotateDirection = rotateOptions.direction;
if (rotateDirection === Enums_1.RotateDirection.random) {
const index = Math.floor(Math.random() * 2);
rotateDirection = index > 0 ? Enums_1.RotateDirection.counterClockwise : Enums_1.RotateDirection.clockwise;
}
switch (rotateDirection) {
case Enums_1.RotateDirection.counterClockwise:
case "counterClockwise":
this.rotate.status = Enums_1.AnimationStatus.decreasing;
break;
case Enums_1.RotateDirection.clockwise:
this.rotate.status = Enums_1.AnimationStatus.increasing;
break;
}
const rotateAnimation = this.options.rotate.animation;
if (rotateAnimation.enable) {
this.rotate.velocity = (rotateAnimation.speed / 360) * container.retina.reduceFactor;
if (!rotateAnimation.sync) {
this.rotate.velocity *= Math.random();
}
}
const sizeAnimation = this.options.size.animation;
if (sizeAnimation.enable) {
this.size.status = Enums_1.AnimationStatus.increasing;
if (!randomSize) {
switch (sizeAnimation.startValue) {
case Enums_1.StartValueType.min:
this.size.value = sizeAnimation.minimumValue * pxRatio;
break;
case Enums_1.StartValueType.random:
this.size.value = Utils_1.NumberUtils.randomInRange(Utils_1.NumberUtils.setRangeValue(sizeAnimation.minimumValue * pxRatio, this.size.value));
break;
case Enums_1.StartValueType.max:
default:
this.size.status = Enums_1.AnimationStatus.decreasing;
break;
}
}
this.size.velocity =
(((_g = this.sizeAnimationSpeed) !== null && _g !== void 0 ? _g : container.retina.sizeAnimationSpeed) / 100) *
container.retina.reduceFactor;
if (!sizeAnimation.sync) {
this.size.velocity *= Math.random();
}
}
const hslColor = Utils_1.ColorUtils.colorToHsl(color, this.id, reduceDuplicates);
if (hslColor) {
this.color = {
h: {
value: hslColor.h,
},
s: {
value: hslColor.s,
},
l: {
value: hslColor.l,
},
};
const colorAnimation = this.options.color.animation;
this.setColorAnimation(colorAnimation.h, this.color.h);
this.setColorAnimation(colorAnimation.s, this.color.s);
this.setColorAnimation(colorAnimation.l, this.color.l);
}
this.position = this.calcPosition(this.container, position);
this.initialPosition = this.position.copy();
this.offset = Vector_1.Vector.create(0, 0);
const opacityOptions = this.options.opacity;
const randomOpacity = typeof opacityOptions.random === "boolean" ? opacityOptions.random : opacityOptions.random.enable;
this.opacity = {
value: Utils_1.NumberUtils.getValue(opacityOptions),
};
const opacityAnimation = opacityOptions.animation;
if (opacityAnimation.enable) {
this.opacity.status = Enums_1.AnimationStatus.increasing;
if (!randomOpacity) {
switch (opacityAnimation.startValue) {
case Enums_1.StartValueType.min:
this.opacity.value = opacityAnimation.minimumValue;
break;
case Enums_1.StartValueType.random:
this.opacity.value = Utils_1.NumberUtils.randomInRange(Utils_1.NumberUtils.setRangeValue(opacityAnimation.minimumValue, this.opacity.value));
break;
case Enums_1.StartValueType.max:
default:
this.opacity.status = Enums_1.AnimationStatus.decreasing;
break;
}
}
this.opacity.velocity = (opacityAnimation.speed / 100) * container.retina.reduceFactor;
if (!opacityAnimation.sync) {
this.opacity.velocity *= Math.random();
}
}
this.sides = 24;
let drawer = container.drawers.get(this.shape);
if (!drawer) {
drawer = Utils_1.Plugins.getShapeDrawer(this.shape);
if (drawer) {
container.drawers.set(this.shape, drawer);
}
}
const sideCountFunc = drawer === null || drawer === void 0 ? void 0 : drawer.getSidesCount;
if (sideCountFunc) {
this.sides = sideCountFunc(this);
}
const imageShape = this.loadImageShape(container, drawer);
if (imageShape) {
this.image = imageShape.image;
this.fill = imageShape.fill;
this.close = imageShape.close;
}
this.stroke =
this.options.stroke instanceof Array
? Utils_1.Utils.itemFromArray(this.options.stroke, this.id, reduceDuplicates)
: this.options.stroke;
this.strokeWidth = this.stroke.width * container.retina.pixelRatio;
const strokeHslColor = (_h = Utils_1.ColorUtils.colorToHsl(this.stroke.color)) !== null && _h !== void 0 ? _h : this.getFillColor();
if (strokeHslColor) {
this.strokeColor = {
h: {
value: strokeHslColor.h,
},
s: {
value: strokeHslColor.s,
},
l: {
value: strokeHslColor.l,
},
};
const strokeColorAnimation = (_j = this.stroke.color) === null || _j === void 0 ? void 0 : _j.animation;
if (strokeColorAnimation && this.strokeColor) {
this.setColorAnimation(strokeColorAnimation.h, this.strokeColor.h);
this.setColorAnimation(strokeColorAnimation.s, this.strokeColor.s);
this.setColorAnimation(strokeColorAnimation.l, this.strokeColor.l);
}
}
const lifeOptions = particlesOptions.life;
this.lifeDelay = container.retina.reduceFactor
? ((Utils_1.NumberUtils.getValue(lifeOptions.delay) * (lifeOptions.delay.sync ? 1 : Math.random())) /
container.retina.reduceFactor) *
1000
: 0;
this.lifeDelayTime = 0;
this.lifeDuration = container.retina.reduceFactor
? ((Utils_1.NumberUtils.getValue(lifeOptions.duration) * (lifeOptions.duration.sync ? 1 : Math.random())) /
container.retina.reduceFactor) *
1000
: 0;
this.lifeTime = 0;
this.livesRemaining = particlesOptions.life.count;
this.spawning = this.lifeDelay > 0;
if (this.lifeDuration <= 0) {
this.lifeDuration = -1;
}
if (this.livesRemaining <= 0) {
this.livesRemaining = -1;
}
this.shadowColor = Utils_1.ColorUtils.colorToRgb(this.options.shadow.color);
this.updater = new Updater_1.Updater(container, this);
this.infecter = new Infecter_1.Infecter(container);
this.mover = new Mover_1.Mover(container, this);
if (drawer && drawer.particleInit) {
drawer.particleInit(container, this);
}
}
move(delta) {
this.mover.move(delta);
}
update(delta) {
this.updater.update(delta);
}
draw(delta) {
this.container.canvas.drawParticle(this, delta);
}
getPosition() {
return this.position.add(this.offset);
}
getRadius() {
return this.bubble.radius || this.size.value;
}
getMass() {
const radius = this.getRadius();
return (Math.pow(radius, 2) * Math.PI) / 2;
}
getFillColor() {
var _a;
return (_a = this.bubble.color) !== null && _a !== void 0 ? _a : Utils_1.ColorUtils.getHslFromAnimation(this.color);
}
getStrokeColor() {
var _a, _b;
return (_b = (_a = this.bubble.color) !== null && _a !== void 0 ? _a : Utils_1.ColorUtils.getHslFromAnimation(this.strokeColor)) !== null && _b !== void 0 ? _b : this.getFillColor();
}
destroy(override) {
this.destroyed = true;
this.bubble.inRange = false;
this.links = [];
if (this.unbreakable) {
return;
}
this.destroyed = true;
this.bubble.inRange = false;
for (const [, plugin] of this.container.plugins) {
if (plugin.particleDestroyed) {
plugin.particleDestroyed(this, override);
}
}
if (override) {
return;
}
const destroyOptions = this.options.destroy;
if (destroyOptions.mode === Enums_1.DestroyMode.split) {
this.split();
}
}
reset() {
this.loops.opacity = 0;
this.loops.size = 0;
}
split() {
const splitOptions = this.options.destroy.split;
if (splitOptions.count >= 0 && this.splitCount++ > splitOptions.count) {
return;
}
const rate = Utils_1.NumberUtils.getRangeValue(splitOptions.rate.value);
for (let i = 0; i < rate; i++) {
this.container.particles.addSplitParticle(this);
}
}
setColorAnimation(colorAnimation, colorValue) {
if (colorAnimation.enable) {
colorValue.velocity = (colorAnimation.speed / 100) * this.container.retina.reduceFactor;
if (colorAnimation.sync) {
return;
}
colorValue.status = Enums_1.AnimationStatus.increasing;
colorValue.velocity *= Math.random();
if (colorValue.value) {
colorValue.value *= Math.random();
}
}
else {
colorValue.velocity = 0;
}
}
calcPosition(container, position, tryCount = 0) {
var _a, _b;
for (const [, plugin] of container.plugins) {
const pluginPos = plugin.particlePosition !== undefined ? plugin.particlePosition(position, this) : undefined;
if (pluginPos !== undefined) {
return Vector_1.Vector.create(pluginPos.x, pluginPos.y);
}
}
const pos = Vector_1.Vector.create((_a = position === null || position === void 0 ? void 0 : position.x) !== null && _a !== void 0 ? _a : Math.random() * container.canvas.size.width, (_b = position === null || position === void 0 ? void 0 : position.y) !== null && _b !== void 0 ? _b : Math.random() * container.canvas.size.height);
const outMode = this.options.move.outMode;
if (Utils_1.Utils.isInArray(outMode, Enums_1.OutMode.bounce) || Utils_1.Utils.isInArray(outMode, Enums_1.OutMode.bounceHorizontal)) {
if (pos.x > container.canvas.size.width - this.size.value * 2) {
pos.x -= this.size.value;
}
else if (pos.x < this.size.value * 2) {
pos.x += this.size.value;
}
}
if (Utils_1.Utils.isInArray(outMode, Enums_1.OutMode.bounce) || Utils_1.Utils.isInArray(outMode, Enums_1.OutMode.bounceVertical)) {
if (pos.y > container.canvas.size.height - this.size.value * 2) {
pos.y -= this.size.value;
}
else if (pos.y < this.size.value * 2) {
pos.y += this.size.value;
}
}
if (this.checkOverlap(pos, tryCount)) {
return this.calcPosition(container, undefined, tryCount + 1);
}
return pos;
}
checkOverlap(pos, tryCount = 0) {
const overlapOptions = this.options.collisions.overlap;
if (!overlapOptions.enable) {
const retries = overlapOptions.retries;
if (retries >= 0 && tryCount > retries) {
throw new Error("Particle is overlapping and can't be placed");
}
let overlaps = false;
for (const particle of this.container.particles.array) {
if (Utils_1.NumberUtils.getDistance(pos, particle.position) < this.size.value + particle.size.value) {
overlaps = true;
break;
}
}
return overlaps;
}
return false;
}
calculateVelocity() {
const baseVelocity = Utils_1.NumberUtils.getParticleBaseVelocity(this.direction);
const res = baseVelocity.copy();
const moveOptions = this.options.move;
let rad;
let radOffset = Math.PI / 4;
if (typeof moveOptions.angle === "number") {
rad = (Math.PI / 180) * moveOptions.angle;
}
else {
rad = (Math.PI / 180) * moveOptions.angle.value;
radOffset = (Math.PI / 180) * moveOptions.angle.offset;
}
const range = {
left: Math.sin(radOffset + rad / 2) - Math.sin(radOffset - rad / 2),
right: Math.cos(radOffset + rad / 2) - Math.cos(radOffset - rad / 2),
};
if (!moveOptions.straight || moveOptions.random) {
res.x += Utils_1.NumberUtils.randomInRange(Utils_1.NumberUtils.setRangeValue(range.left, range.right)) / 2;
res.y += Utils_1.NumberUtils.randomInRange(Utils_1.NumberUtils.setRangeValue(range.left, range.right)) / 2;
}
return res;
}
loadImageShape(container, drawer) {
var _a, _b, _c, _d, _e;
if (!(this.shape === Enums_1.ShapeType.image || this.shape === Enums_1.ShapeType.images)) {
return;
}
const imageDrawer = drawer;
const images = imageDrawer.getImages(container).images;
const imageData = this.shapeData;
const image = (_a = images.find((t) => t.source === imageData.src)) !== null && _a !== void 0 ? _a : images[0];
const color = this.getFillColor();
let imageRes;
if (!image) {
return;
}
if (image.svgData !== undefined && imageData.replaceColor && color) {
const svgColoredData = Utils_1.ColorUtils.replaceColorSvg(image, color, this.opacity.value);
const svg = new Blob([svgColoredData], { type: "image/svg+xml" });
const domUrl = URL || window.URL || window.webkitURL || window;
const url = domUrl.createObjectURL(svg);
const img = new Image();
imageRes = {
data: image,
loaded: false,
ratio: imageData.width / imageData.height,
replaceColor: (_b = imageData.replaceColor) !== null && _b !== void 0 ? _b : imageData.replace_color,
source: imageData.src,
};
img.addEventListener("load", () => {
if (this.image) {
this.image.loaded = true;
image.element = img;
}
domUrl.revokeObjectURL(url);
});
img.addEventListener("error", () => {
domUrl.revokeObjectURL(url);
Utils_1.Utils.loadImage(imageData.src).then((img2) => {
if (this.image && img2) {
image.element = img2.element;
this.image.loaded = true;
}
});
});
img.src = url;
}
else {
imageRes = {
data: image,
loaded: true,
ratio: imageData.width / imageData.height,
replaceColor: (_c = imageData.replaceColor) !== null && _c !== void 0 ? _c : imageData.replace_color,
source: imageData.src,
};
}
if (!imageRes.ratio) {
imageRes.ratio = 1;
}
const fill = (_d = imageData.fill) !== null && _d !== void 0 ? _d : this.fill;
const close = (_e = imageData.close) !== null && _e !== void 0 ? _e : this.close;
return {
image: imageRes,
fill,
close,
};
}
}
exports.Particle = Particle;