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.
421 lines (420 loc) • 18.5 kB
JavaScript
import { NumberUtils, Utils } from "../../Utils";
import { AnimationStatus, DestroyType, OutMode } from "../../Enums";
import { OutModeDirection } from "../../Enums/Directions/OutModeDirection";
function bounceHorizontal(data) {
if (data.outMode === OutMode.bounce ||
data.outMode === OutMode.bounceHorizontal ||
data.outMode === "bounceHorizontal" ||
data.outMode === OutMode.split) {
const velocity = data.particle.velocity.x;
let bounced = false;
if ((data.direction === OutModeDirection.right && data.bounds.right >= data.canvasSize.width && velocity > 0) ||
(data.direction === OutModeDirection.left && data.bounds.left <= 0 && velocity < 0)) {
const newVelocity = NumberUtils.getValue(data.particle.options.bounce.horizontal);
data.particle.velocity.x *= -newVelocity;
bounced = true;
}
if (!bounced) {
return;
}
const minPos = data.offset.x + data.size;
if (data.bounds.right >= data.canvasSize.width) {
data.particle.position.x = data.canvasSize.width - minPos;
}
else if (data.bounds.left <= 0) {
data.particle.position.x = minPos;
}
if (data.outMode === OutMode.split) {
data.particle.destroy();
}
}
}
function bounceVertical(data) {
if (data.outMode === OutMode.bounce ||
data.outMode === OutMode.bounceVertical ||
data.outMode === "bounceVertical" ||
data.outMode === OutMode.split) {
const velocity = data.particle.velocity.y;
let bounced = false;
if ((data.direction === OutModeDirection.bottom &&
data.bounds.bottom >= data.canvasSize.height &&
velocity > 0) ||
(data.direction === OutModeDirection.top && data.bounds.top <= 0 && velocity < 0)) {
const newVelocity = NumberUtils.getValue(data.particle.options.bounce.vertical);
data.particle.velocity.y *= -newVelocity;
bounced = true;
}
if (!bounced) {
return;
}
const minPos = data.offset.y + data.size;
if (data.bounds.bottom >= data.canvasSize.height) {
data.particle.position.y = data.canvasSize.height - minPos;
}
else if (data.bounds.top <= 0) {
data.particle.position.y = minPos;
}
if (data.outMode === OutMode.split) {
data.particle.destroy();
}
}
}
function checkDestroy(particle, destroy, value, minValue, maxValue) {
switch (destroy) {
case DestroyType.max:
if (value >= maxValue) {
particle.destroy();
}
break;
case DestroyType.min:
if (value <= minValue) {
particle.destroy();
}
break;
}
}
export class Updater {
constructor(container, particle) {
this.container = container;
this.particle = particle;
}
update(delta) {
if (this.particle.destroyed) {
return;
}
this.updateLife(delta);
if (this.particle.destroyed || this.particle.spawning) {
return;
}
this.updateOpacity(delta);
this.updateSize(delta);
this.updateAngle(delta);
this.updateColor(delta);
this.updateStrokeColor(delta);
this.updateOutModes(delta);
}
updateLife(delta) {
const particle = this.particle;
let justSpawned = false;
if (particle.spawning) {
particle.lifeDelayTime += delta.value;
if (particle.lifeDelayTime >= particle.lifeDelay) {
justSpawned = true;
particle.spawning = false;
particle.lifeDelayTime = 0;
particle.lifeTime = 0;
}
}
if (particle.lifeDuration === -1) {
return;
}
if (!particle.spawning) {
if (justSpawned) {
particle.lifeTime = 0;
}
else {
particle.lifeTime += delta.value;
}
if (particle.lifeTime >= particle.lifeDuration) {
particle.lifeTime = 0;
if (particle.livesRemaining > 0) {
particle.livesRemaining--;
}
if (particle.livesRemaining === 0) {
particle.destroy();
return;
}
const canvasSize = this.container.canvas.size;
particle.position.x = NumberUtils.randomInRange(NumberUtils.setRangeValue(0, canvasSize.width));
particle.position.y = NumberUtils.randomInRange(NumberUtils.setRangeValue(0, canvasSize.height));
particle.spawning = true;
particle.lifeDelayTime = 0;
particle.lifeTime = 0;
particle.reset();
const lifeOptions = particle.options.life;
particle.lifeDelay = NumberUtils.getValue(lifeOptions.delay) * 1000;
particle.lifeDuration = NumberUtils.getValue(lifeOptions.duration) * 1000;
}
}
}
updateOpacity(delta) {
var _a, _b;
const particle = this.particle;
const opacityOpt = particle.options.opacity;
const opacityAnim = opacityOpt.animation;
const minValue = NumberUtils.getRangeMin(opacityOpt.value);
const maxValue = NumberUtils.getRangeMax(opacityOpt.value);
if (!particle.destroyed &&
opacityAnim.enable &&
(opacityAnim.count <= 0 || particle.loops.size < opacityAnim.count)) {
switch (particle.opacity.status) {
case AnimationStatus.increasing:
if (particle.opacity.value >= maxValue) {
particle.opacity.status = AnimationStatus.decreasing;
particle.loops.opacity++;
}
else {
particle.opacity.value += ((_a = particle.opacity.velocity) !== null && _a !== void 0 ? _a : 0) * delta.factor;
}
break;
case AnimationStatus.decreasing:
if (particle.opacity.value <= minValue) {
particle.opacity.status = AnimationStatus.increasing;
particle.loops.opacity++;
}
else {
particle.opacity.value -= ((_b = particle.opacity.velocity) !== null && _b !== void 0 ? _b : 0) * delta.factor;
}
break;
}
checkDestroy(particle, opacityAnim.destroy, particle.opacity.value, minValue, maxValue);
if (!particle.destroyed) {
particle.opacity.value = NumberUtils.clamp(particle.opacity.value, minValue, maxValue);
}
}
}
updateSize(delta) {
var _a;
const container = this.container;
const particle = this.particle;
const sizeOpt = particle.options.size;
const sizeAnim = sizeOpt.animation;
const sizeVelocity = ((_a = particle.size.velocity) !== null && _a !== void 0 ? _a : 0) * delta.factor;
const minValue = NumberUtils.getRangeMin(sizeOpt.value) * container.retina.pixelRatio;
const maxValue = NumberUtils.getRangeMax(sizeOpt.value) * container.retina.pixelRatio;
if (!particle.destroyed && sizeAnim.enable && (sizeAnim.count <= 0 || particle.loops.size < sizeAnim.count)) {
switch (particle.size.status) {
case AnimationStatus.increasing:
if (particle.size.value >= maxValue) {
particle.size.status = AnimationStatus.decreasing;
particle.loops.size++;
}
else {
particle.size.value += sizeVelocity;
}
break;
case AnimationStatus.decreasing:
if (particle.size.value <= minValue) {
particle.size.status = AnimationStatus.increasing;
particle.loops.size++;
}
else {
particle.size.value -= sizeVelocity;
}
}
checkDestroy(particle, sizeAnim.destroy, particle.size.value, minValue, maxValue);
if (!particle.destroyed) {
particle.size.value = NumberUtils.clamp(particle.size.value, minValue, maxValue);
}
}
}
updateAngle(delta) {
var _a;
const particle = this.particle;
const rotate = particle.options.rotate;
const rotateAnimation = rotate.animation;
const speed = ((_a = particle.rotate.velocity) !== null && _a !== void 0 ? _a : 0) * delta.factor;
const max = 2 * Math.PI;
if (rotateAnimation.enable) {
switch (particle.rotate.status) {
case AnimationStatus.increasing:
particle.rotate.value += speed;
if (particle.rotate.value > max) {
particle.rotate.value -= max;
}
break;
case AnimationStatus.decreasing:
default:
particle.rotate.value -= speed;
if (particle.rotate.value < 0) {
particle.rotate.value += max;
}
break;
}
}
}
updateColor(delta) {
var _a, _b, _c;
const particle = this.particle;
const animationOptions = particle.options.color.animation;
if (((_a = particle.color) === null || _a === void 0 ? void 0 : _a.h) !== undefined) {
this.updateColorValue(particle, delta, particle.color.h, animationOptions.h, 360, false);
}
if (((_b = particle.color) === null || _b === void 0 ? void 0 : _b.s) !== undefined) {
this.updateColorValue(particle, delta, particle.color.s, animationOptions.s, 100, true);
}
if (((_c = particle.color) === null || _c === void 0 ? void 0 : _c.l) !== undefined) {
this.updateColorValue(particle, delta, particle.color.l, animationOptions.l, 100, true);
}
}
updateStrokeColor(delta) {
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m;
const particle = this.particle;
if (!particle.stroke.color) {
return;
}
const animationOptions = particle.stroke.color.animation;
const valueAnimations = animationOptions;
if (valueAnimations.enable !== undefined) {
const hue = (_b = (_a = particle.strokeColor) === null || _a === void 0 ? void 0 : _a.h) !== null && _b !== void 0 ? _b : (_c = particle.color) === null || _c === void 0 ? void 0 : _c.h;
if (hue) {
this.updateColorValue(particle, delta, hue, valueAnimations, 360, false);
}
}
else {
const hslAnimations = animationOptions;
const h = (_e = (_d = particle.strokeColor) === null || _d === void 0 ? void 0 : _d.h) !== null && _e !== void 0 ? _e : (_f = particle.color) === null || _f === void 0 ? void 0 : _f.h;
if (h) {
this.updateColorValue(particle, delta, h, hslAnimations.h, 360, false);
}
const s = (_h = (_g = particle.strokeColor) === null || _g === void 0 ? void 0 : _g.s) !== null && _h !== void 0 ? _h : (_j = particle.color) === null || _j === void 0 ? void 0 : _j.s;
if (s) {
this.updateColorValue(particle, delta, s, hslAnimations.s, 100, true);
}
const l = (_l = (_k = particle.strokeColor) === null || _k === void 0 ? void 0 : _k.l) !== null && _l !== void 0 ? _l : (_m = particle.color) === null || _m === void 0 ? void 0 : _m.l;
if (l) {
this.updateColorValue(particle, delta, l, hslAnimations.l, 100, true);
}
}
}
updateColorValue(particle, delta, value, valueAnimation, max, decrease) {
var _a;
const colorValue = value;
if (!colorValue || !valueAnimation.enable) {
return;
}
const offset = NumberUtils.randomInRange(valueAnimation.offset);
const velocity = ((_a = value.velocity) !== null && _a !== void 0 ? _a : 0) * delta.factor + offset * 3.6;
if (!decrease || colorValue.status === AnimationStatus.increasing) {
colorValue.value += velocity;
if (decrease && colorValue.value > max) {
colorValue.status = AnimationStatus.decreasing;
colorValue.value -= colorValue.value % max;
}
}
else {
colorValue.value -= velocity;
if (colorValue.value < 0) {
colorValue.status = AnimationStatus.increasing;
colorValue.value += colorValue.value;
}
}
if (colorValue.value > max) {
colorValue.value %= max;
}
}
updateOutModes(delta) {
var _a, _b, _c, _d;
const outModes = this.particle.options.move.outModes;
this.updateOutMode(delta, (_a = outModes.bottom) !== null && _a !== void 0 ? _a : outModes.default, OutModeDirection.bottom);
this.updateOutMode(delta, (_b = outModes.left) !== null && _b !== void 0 ? _b : outModes.default, OutModeDirection.left);
this.updateOutMode(delta, (_c = outModes.right) !== null && _c !== void 0 ? _c : outModes.default, OutModeDirection.right);
this.updateOutMode(delta, (_d = outModes.top) !== null && _d !== void 0 ? _d : outModes.default, OutModeDirection.top);
}
updateOutMode(delta, outMode, direction) {
const container = this.container;
const particle = this.particle;
switch (outMode) {
case OutMode.bounce:
case OutMode.bounceVertical:
case OutMode.bounceHorizontal:
case "bounceVertical":
case "bounceHorizontal":
case OutMode.split:
this.updateBounce(delta, direction, outMode);
break;
case OutMode.destroy:
if (!Utils.isPointInside(particle.position, container.canvas.size, particle.getRadius(), direction)) {
container.particles.remove(particle, true);
}
break;
case OutMode.out:
if (!Utils.isPointInside(particle.position, container.canvas.size, particle.getRadius(), direction)) {
this.fixOutOfCanvasPosition(direction);
}
break;
case OutMode.none:
this.bounceNone(direction);
break;
}
}
fixOutOfCanvasPosition(direction) {
const container = this.container;
const particle = this.particle;
const wrap = particle.options.move.warp;
const canvasSize = container.canvas.size;
const newPos = {
bottom: canvasSize.height + particle.getRadius() - particle.offset.y,
left: -particle.getRadius() - particle.offset.x,
right: canvasSize.width + particle.getRadius() + particle.offset.x,
top: -particle.getRadius() - particle.offset.y,
};
const sizeValue = particle.getRadius();
const nextBounds = Utils.calculateBounds(particle.position, sizeValue);
if (direction === OutModeDirection.right && nextBounds.left > canvasSize.width - particle.offset.x) {
particle.position.x = newPos.left;
if (!wrap) {
particle.position.y = Math.random() * canvasSize.height;
}
}
else if (direction === OutModeDirection.left && nextBounds.right < -particle.offset.x) {
particle.position.x = newPos.right;
if (!wrap) {
particle.position.y = Math.random() * canvasSize.height;
}
}
if (direction === OutModeDirection.bottom && nextBounds.top > canvasSize.height - particle.offset.y) {
if (!wrap) {
particle.position.x = Math.random() * canvasSize.width;
}
particle.position.y = newPos.top;
}
else if (direction === OutModeDirection.top && nextBounds.bottom < -particle.offset.y) {
if (!wrap) {
particle.position.x = Math.random() * canvasSize.width;
}
particle.position.y = newPos.bottom;
}
}
updateBounce(delta, direction, outMode) {
const container = this.container;
const particle = this.particle;
let handled = false;
for (const [, plugin] of container.plugins) {
if (plugin.particleBounce !== undefined) {
handled = plugin.particleBounce(particle, delta, direction);
}
if (handled) {
break;
}
}
if (handled) {
return;
}
const pos = particle.getPosition(), offset = particle.offset, size = particle.getRadius(), bounds = Utils.calculateBounds(pos, size), canvasSize = container.canvas.size;
bounceHorizontal({ particle, outMode, direction, bounds, canvasSize, offset, size });
bounceVertical({ particle, outMode, direction, bounds, canvasSize, offset, size });
}
bounceNone(direction) {
const particle = this.particle;
if (particle.options.move.distance) {
return;
}
const gravityOptions = particle.options.move.gravity;
const container = this.container;
if (!gravityOptions.enable) {
if (!Utils.isPointInside(particle.position, container.canvas.size, particle.getRadius(), direction)) {
container.particles.remove(particle);
}
}
else {
const position = particle.position;
if ((gravityOptions.acceleration >= 0 &&
position.y > container.canvas.size.height &&
direction === OutModeDirection.bottom) ||
(gravityOptions.acceleration < 0 && position.y < 0 && direction === OutModeDirection.top)) {
container.particles.remove(particle);
}
}
}
}