Merge pull request 'Added background animation to habe a little bit more interesting page' (#27) from feature/backgroundAnimation into main
Some checks failed
Build, Test & Push Frontend / quality-check (push) Failing after 44s
Build, Test & Push Frontend / docker (push) Has been skipped

Reviewed-on: #27
This commit was merged in pull request #27.
This commit is contained in:
2026-02-23 10:01:28 +01:00
9 changed files with 128 additions and 16 deletions

View File

@@ -1,6 +1,6 @@
<app-particles-background></app-particles-background>
<app-topbar /> <app-topbar />
<main class="app-container app-surface">
<main class="algo-container app-surface">
<router-outlet /> <router-outlet />
</main> </main>

View File

@@ -2,12 +2,13 @@ import { Component } from '@angular/core';
import { RouterOutlet } from '@angular/router'; import { RouterOutlet } from '@angular/router';
import {TopbarComponent} from '../topbar/topbar.component'; import {TopbarComponent} from '../topbar/topbar.component';
import {TranslatePipe} from '@ngx-translate/core'; import {TranslatePipe} from '@ngx-translate/core';
import {ParticleBackgroundComponent} from '../../shared/components/particles-background/particles-background.component';
@Component({ @Component({
selector: 'app-root', selector: 'app-root',
standalone: true, standalone: true,
imports: [RouterOutlet, TopbarComponent, TranslatePipe], imports: [RouterOutlet, TopbarComponent, TranslatePipe, ParticleBackgroundComponent],
templateUrl: './app.component.html', templateUrl: './app.component.html',
styleUrl: './app.component.scss' styleUrl: './app.component.scss'
}) })

View File

@@ -0,0 +1 @@
<canvas #canvas></canvas>

View File

@@ -0,0 +1,14 @@
:host {
position: fixed;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
z-index: -1;
pointer-events: none;
}
canvas {
display: block;
width: 100%;
height: 100%;
}

View File

@@ -0,0 +1,102 @@
import {AfterViewInit, Component, ElementRef, HostListener, NgZone, OnDestroy, ViewChild} from '@angular/core';
@Component({
selector: 'app-particles-background',
imports: [],
templateUrl: './particles-background.component.html',
styleUrl: './particles-background.component.scss',
})
export class ParticleBackgroundComponent implements AfterViewInit, OnDestroy {
@ViewChild('canvas', { static: true }) canvasRef!: ElementRef<HTMLCanvasElement>;
private ctx!: CanvasRenderingContext2D;
private particles: any[] = [];
private animationFrameId: number = 0;
// --- Configuration ---
private readonly numParticles = 80;
private readonly maxDistance = 150;
private readonly particleSpeed = 0.8;
constructor(private ngZone: NgZone) {}
ngAfterViewInit(): void {
const canvas = this.canvasRef.nativeElement;
this.ctx = canvas.getContext('2d')!;
this.resizeCanvas();
this.initParticles();
this.ngZone.runOutsideAngular(() => {
this.animate();
});
}
ngOnDestroy(): void {
cancelAnimationFrame(this.animationFrameId);
}
@HostListener('window:resize')
resizeCanvas(): void {
const canvas = this.canvasRef.nativeElement;
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
}
private initParticles(): void {
this.particles = [];
const canvas = this.canvasRef.nativeElement;
for (let i = 0; i < this.numParticles; i++) {
this.particles.push({
x: Math.random() * canvas.width,
y: Math.random() * canvas.height,
vx: (Math.random() - 0.5) * this.particleSpeed,
vy: (Math.random() - 0.5) * this.particleSpeed,
radius: Math.random() * 1.5 + 0.5
});
}
}
private animate = (): void => {
const canvas = this.canvasRef.nativeElement;
this.ctx.clearRect(0, 0, canvas.width, canvas.height);
for (let i = 0; i < this.numParticles; i++) {
let p = this.particles[i];
p.x += p.vx;
p.y += p.vy;
if (p.x < 0 || p.x > canvas.width) p.vx *= -1;
if (p.y < 0 || p.y > canvas.height) p.vy *= -1;
this.ctx.beginPath();
this.ctx.arc(p.x, p.y, p.radius, 0, Math.PI * 2);
this.ctx.fillStyle = 'rgba(120, 150, 170, 0.4)';
this.ctx.fill();
for (let j = i + 1; j < this.numParticles; j++) {
let p2 = this.particles[j];
let dx = p.x - p2.x;
let dy = p.y - p2.y;
let distance = Math.sqrt(dx * dx + dy * dy);
if (distance < this.maxDistance) {
this.ctx.beginPath();
this.ctx.moveTo(p.x, p.y);
this.ctx.lineTo(p2.x, p2.y);
let opacity = (1 - (distance / this.maxDistance)) * 0.5;
this.ctx.strokeStyle = `rgba(120, 150, 170, ${opacity})`;
this.ctx.lineWidth = 1;
this.ctx.stroke();
}
}
}
this.animationFrameId = requestAnimationFrame(this.animate);
};
}

View File

@@ -1 +0,0 @@
<p>particles-background works!</p>

View File

@@ -1,11 +0,0 @@
import { Component } from '@angular/core';
@Component({
selector: 'app-particles-background',
imports: [],
templateUrl: './particles-background.html',
styleUrl: './particles-background.scss',
})
export class ParticlesBackground {
}

View File

@@ -407,9 +407,15 @@ app-root {
min-height: 100vh; min-height: 100vh;
} }
.app-container {
width: 100%;
max-width: var(--app-maxWidth);
margin: 1rem auto;
}
.app-surface { .app-surface {
flex-grow: 1; flex-grow: 1;
background: var(--app-bg);
color: var(--app-fg); color: var(--app-fg);
transition: background-color 220ms ease, color 220ms ease; transition: background-color 220ms ease, color 220ms ease;
} }