<canvas id="canvas"></canvas>
const NUMBER_OF_PARTICLES = 50;
const MAX_PARTICLE_SIZE = 20;
const MAX_SPEED = 5;
const TIMEOUT_FACTOR = 600 / NUMBER_OF_PARTICLES;
const canvas = document.querySelector("#canvas");
canvasResize(canvas);
window.addEventListener("resize", () => canvasResize(canvas));
const ctx = canvas.getContext("2d", {
alpha: false
});
function Particle(options) {
Object.assign(this, options);
this.vy = Math.random() * MAX_SPEED - MAX_SPEED / 2;
this.vx = Math.random() * MAX_SPEED - MAX_SPEED / 2;
this.initialV = {
x: Math.abs(this.vx),
y: Math.abs(this.vy)
};
}
Particle.prototype.moveToPoint = function (x, y, force = null) {
const deltaX = (x - this.x) / canvas.width;
const deltaY = (y - this.y) / canvas.width;
force ??= this.getDistance(x, y) * 0.2;
this.vx = deltaX * force;
this.vy = deltaY * force;
};
Particle.prototype.nextMove = function () {
const maxY = canvas.height - this.size;
const maxX = canvas.width - this.size;
const minX = this.size;
const minY = this.size;
// Isso aqui desacelera a parada
this.vx =
Math.sign(this.vx) * Math.max(this.initialV.x, Math.abs(this.vx) - 0.05);
this.vy =
Math.sign(this.vy) * Math.max(this.initialV.y, Math.abs(this.vy) - 0.05);
this.x = Math.min(maxX, Math.max(this.x + this.vx, minX));
this.y = Math.min(maxY, Math.max(this.y + this.vy, minY));
if (this.y >= maxY || this.y <= minY) {
this.vy *= -1;
}
if (this.x >= maxX || this.x <= minX) {
this.vx *= -1;
}
return this;
};
Particle.prototype.draw = function (ctx) {
const offset = canvas.width * 10;
ctx.beginPath();
ctx.fillStyle = this.color;
ctx.shadowOffsetX = 2 + offset;
ctx.shadowOffsetY = 2 + offset;
ctx.shadowBlur = this.size;
ctx.shadowColor = this.color;
this.nextMove();
ctx.arc(this.x - offset, this.y - offset, this.size, 0, Math.PI * 2, true);
ctx.closePath();
ctx.fill();
};
Particle.prototype.getRandomSpeed = function () {
return Math.random() * MAX_SPEED - MAX_SPEED / 2;
};
Particle.prototype.getRelativePosition = function (x, y) {
return { x: this.x - x, y: this.y - y };
};
Particle.prototype.getDistance = function (inputX, inputY) {
const { x, y } = this.getRelativePosition(inputX, inputY);
return Math.sqrt(x * x + y * y);
};
Particle.prototype.inPoints = function (x, y, radius = 1) {
return this.getDistance(x, y) < this.size + radius;
};
const particles = Array.from({
length: NUMBER_OF_PARTICLES
}).map(() => {
return new Particle({
y: Math.random() * canvas.height,
x: Math.random() * canvas.width,
color: "#ff00ff",
size: Math.random() * MAX_PARTICLE_SIZE
});
});
function nextFrame() {
// isso remove o efeito de blur
// context.clearRect(0,0,canvas.width, canvas.height);
// com opacity menor que 1 fica o "blur motion"
ctx.fillStyle = "rgba(0, 0, 0, 1)";
ctx.fillRect(0, 0, canvas.width, canvas.height);
particles.forEach((p) => p.draw(ctx));
requestAnimationFrame(nextFrame);
}
function canvasResize(canvas) {
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
}
nextFrame();
const handleMovement = (eventX, eventY) => {
const rect = canvas.getBoundingClientRect();
const scaleX = canvas.width / rect.width;
const scaleY = canvas.height / rect.height;
let x = (eventX - rect.left) * scaleX;
let y = (eventY - rect.top) * scaleY;
particles.forEach((p, i) => {
setTimeout(() => {
p.moveToPoint(x, y);
}, i * 10);
});
};
canvas.addEventListener("mousemove", (e) =>
handleMovement(e.clientX, e.clientY)
);
canvas.addEventListener("mousedown", (e) => {
particles.forEach((particle, index) => {
if (Math.abs(particle.vx) >= 10) return;
particle.moveToPoint(e.clientX, e.clientY, -50);
});
});
canvas.addEventListener(
"touchmove",
(e) => {
const x = e.touches[0].clientX;
const y = e.touches[0].clientY;
handleMovement(x, y);
},
{ passive: true }
);