Added background animation to habe a little bit more interesting page
This commit is contained in:
@@ -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>
|
||||||
|
|
||||||
|
|||||||
@@ -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'
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
<canvas #canvas></canvas>
|
||||||
@@ -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%;
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -1 +0,0 @@
|
|||||||
<p>particles-background works!</p>
|
|
||||||
@@ -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 {
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user