frontend/src/lib/components/ParticleEffect.svelte
2024-07-13 12:13:29 +02:00

158 lines
4.8 KiB
Svelte

<script lang="ts">
import { onMount, onDestroy } from 'svelte';
export let quantity = 30;
export let staticity = 50;
export let ease = 50;
let canvas: HTMLCanvasElement;
let canvasContainer: HTMLDivElement;
class ParticleAnimation {
canvas: HTMLCanvasElement;
context: CanvasRenderingContext2D;
dpr: number;
settings: { quantity: number; staticity: number; ease: number };
circles: any[];
canvasSize: { w: number; h: number };
initCanvas: () => void;
resizeCanvas: () => void;
drawCircle: (circle: any, update?: boolean) => void;
drawParticles: () => void;
animate: () => void;
constructor(el: HTMLCanvasElement, options = {}) {
this.canvas = el;
if (!this.canvas) return;
this.context = this.canvas.getContext('2d');
this.dpr = typeof window !== 'undefined' ? window.devicePixelRatio || 1 : 1;
this.settings = {
quantity: options.quantity || 30,
staticity: options.staticity || 50,
ease: options.ease || 50,
};
this.circles = [];
this.canvasSize = {
w: 0,
h: 0,
};
this.initCanvas = this.initCanvas.bind(this);
this.resizeCanvas = this.resizeCanvas.bind(this);
this.drawCircle = this.drawCircle.bind(this);
this.drawParticles = this.drawParticles.bind(this);
this.animate = this.animate.bind(this);
this.init();
}
init() {
this.initCanvas();
this.animate();
if (typeof window !== 'undefined') {
window.addEventListener('resize', this.initCanvas);
}
}
initCanvas() {
this.resizeCanvas();
this.drawParticles();
}
resizeCanvas() {
this.circles.length = 0;
this.canvasSize.w = this.canvas.parentElement.offsetWidth;
this.canvasSize.h = this.canvas.parentElement.offsetHeight;
this.canvas.width = this.canvasSize.w * this.dpr;
this.canvas.height = this.canvasSize.h * this.dpr;
this.canvas.style.width = this.canvasSize.w + 'px';
this.canvas.style.height = this.canvasSize.h + 'px';
this.context.scale(this.dpr, this.dpr);
}
circleParams() {
const x = Math.floor(Math.random() * this.canvasSize.w);
const y = Math.floor(Math.random() * this.canvasSize.h);
const translateX = 0;
const translateY = 0;
const size = Math.floor(Math.random() * 2) + 1;
const alpha = 0;
const targetAlpha = parseFloat((Math.random() * 0.6 + 0.1).toFixed(1));
const dx = (Math.random() - 0.5) * 0.2;
const dy = (Math.random() - 0.5) * 0.2;
return { x, y, translateX, translateY, size, alpha, targetAlpha, dx, dy };
}
drawCircle(circle, update = false) {
const { x, y, translateX, translateY, size, alpha } = circle;
this.context.translate(translateX, translateY);
this.context.beginPath();
this.context.arc(x, y, size, 0, 2 * Math.PI);
this.context.fillStyle = `rgba(255, 255, 255, ${alpha})`;
this.context.fill();
this.context.setTransform(this.dpr, 0, 0, this.dpr, 0, 0);
if (!update) {
this.circles.push(circle);
}
}
clearContext() {
this.context.clearRect(0, 0, this.canvasSize.w, this.canvasSize.h);
}
drawParticles() {
this.clearContext();
const particleCount = this.settings.quantity;
for (let i = 0; i < particleCount; i++) {
const circle = this.circleParams();
this.drawCircle(circle);
}
}
animate() {
this.clearContext();
this.circles.forEach((circle) => {
const { x, y, dx, dy, alpha, targetAlpha } = circle;
circle.x += dx;
circle.y += dy;
// Wrap particles around the canvas edges
if (circle.x > this.canvasSize.w) circle.x = 0;
if (circle.x < 0) circle.x = this.canvasSize.w;
if (circle.y > this.canvasSize.h) circle.y = 0;
if (circle.y < 0) circle.y = this.canvasSize.h;
circle.alpha += alpha < targetAlpha ? 0.01 : -0.01;
circle.alpha = Math.min(Math.max(circle.alpha, 0), targetAlpha);
this.drawCircle(circle, true);
});
if (typeof window !== 'undefined') {
window.requestAnimationFrame(this.animate);
}
}
}
let animation: ParticleAnimation;
onMount(() => {
if (canvas) {
animation = new ParticleAnimation(canvas, { quantity, staticity, ease });
}
});
onDestroy(() => {
if (typeof window !== 'undefined') {
window.removeEventListener('resize', animation.initCanvas);
}
});
</script>
<div bind:this={canvasContainer}>
<canvas bind:this={canvas} data-particle-animation></canvas>
</div>
<style>
canvas {
width: 100%;
height: 100%;
}
</style>