added background
This commit is contained in:
1
src/app/components/dot-background/dot-background.html
Normal file
1
src/app/components/dot-background/dot-background.html
Normal file
@@ -0,0 +1 @@
|
||||
<canvas #canvas></canvas>
|
||||
9
src/app/components/dot-background/dot-background.scss
Normal file
9
src/app/components/dot-background/dot-background.scss
Normal file
@@ -0,0 +1,9 @@
|
||||
canvas {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
z-index: -1;
|
||||
background: #0a0a0f;
|
||||
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
23
src/app/components/dot-background/dot-background.spec.ts
Normal file
23
src/app/components/dot-background/dot-background.spec.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { DotBackground } from './dot-background';
|
||||
|
||||
describe('DotBackground', () => {
|
||||
let component: DotBackground;
|
||||
let fixture: ComponentFixture<DotBackground>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [DotBackground]
|
||||
})
|
||||
.compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(DotBackground);
|
||||
component = fixture.componentInstance;
|
||||
await fixture.whenStable();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
131
src/app/components/dot-background/dot-background.ts
Normal file
131
src/app/components/dot-background/dot-background.ts
Normal file
@@ -0,0 +1,131 @@
|
||||
import {afterNextRender, AfterViewInit, Component, ElementRef, NgZone, OnDestroy, ViewChild} from '@angular/core';
|
||||
import {Dot} from '../../models/dot';
|
||||
|
||||
@Component({
|
||||
selector: 'app-dot-background',
|
||||
imports: [],
|
||||
templateUrl: './dot-background.html',
|
||||
styleUrl: './dot-background.scss',
|
||||
})
|
||||
export class DotBackground implements OnDestroy {
|
||||
@ViewChild('canvas') canvasRef!: ElementRef<HTMLCanvasElement>;
|
||||
|
||||
private ctx!: CanvasRenderingContext2D;
|
||||
private dots: Dot[] = [];
|
||||
private mouse = { x: -1000, y: -1000 };
|
||||
private animationId = 0;
|
||||
private initialized = false;
|
||||
|
||||
private readonly DOT_COUNT = 12;
|
||||
private readonly COLORS = ['#6366f1', '#8b5cf6', '#a855f7', '#3b82f6'];
|
||||
private readonly MOUSE_RADIUS = 150;
|
||||
|
||||
constructor(private ngZone: NgZone) {
|
||||
afterNextRender(() => {
|
||||
this.init();
|
||||
});
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
if (!this.initialized) return;
|
||||
|
||||
cancelAnimationFrame(this.animationId);
|
||||
window.removeEventListener('resize', this.resize);
|
||||
window.removeEventListener('mousemove', this.onMouseMove);
|
||||
}
|
||||
|
||||
private init() {
|
||||
const canvas = this.canvasRef.nativeElement;
|
||||
this.ctx = canvas.getContext('2d')!;
|
||||
this.resize();
|
||||
this.initDots();
|
||||
|
||||
window.addEventListener('resize', this.resize);
|
||||
window.addEventListener('mousemove', this.onMouseMove);
|
||||
|
||||
this.initialized = true;
|
||||
this.ngZone.runOutsideAngular(() => this.animate());
|
||||
}
|
||||
|
||||
private resize = () => {
|
||||
const canvas = this.canvasRef.nativeElement;
|
||||
canvas.width = window.innerWidth;
|
||||
canvas.height = window.innerHeight;
|
||||
|
||||
// Clamp dots to new bounds
|
||||
for (const dot of this.dots) {
|
||||
dot.x = Math.min(dot.x, canvas.width - dot.radius);
|
||||
dot.y = Math.min(dot.y, canvas.height - dot.radius);
|
||||
dot.x = Math.max(dot.x, dot.radius);
|
||||
dot.y = Math.max(dot.y, dot.radius);
|
||||
}
|
||||
};
|
||||
|
||||
private onMouseMove = (e: MouseEvent) => {
|
||||
this.mouse.x = e.clientX;
|
||||
this.mouse.y = e.clientY;
|
||||
};
|
||||
|
||||
private initDots() {
|
||||
const { width, height } = this.canvasRef.nativeElement;
|
||||
for (let i = 0; i < this.DOT_COUNT; i++) {
|
||||
this.dots.push({
|
||||
x: Math.random() * width,
|
||||
y: Math.random() * height,
|
||||
vx: (Math.random() - 0.5) * 0.5,
|
||||
vy: (Math.random() - 0.5) * 0.5,
|
||||
radius: Math.random() * 60 + 40,
|
||||
color: this.COLORS[i % this.COLORS.length],
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private animate = () => {
|
||||
const canvas = this.canvasRef.nativeElement;
|
||||
const { width, height } = canvas;
|
||||
this.ctx.clearRect(0, 0, width, height);
|
||||
|
||||
for (const dot of this.dots) {
|
||||
const dx = dot.x - this.mouse.x;
|
||||
const dy = dot.y - this.mouse.y;
|
||||
const dist = Math.sqrt(dx * dx + dy * dy);
|
||||
|
||||
if (dist < this.MOUSE_RADIUS) {
|
||||
const force = (this.MOUSE_RADIUS - dist) / this.MOUSE_RADIUS;
|
||||
dot.vx += (dx / dist) * force * 0.5;
|
||||
dot.vy += (dy / dist) * force * 0.5;
|
||||
}
|
||||
|
||||
dot.x += dot.vx;
|
||||
dot.y += dot.vy;
|
||||
|
||||
const speed = Math.sqrt(dot.vx * dot.vx + dot.vy * dot.vy);
|
||||
if (speed > 0.3) {
|
||||
dot.vx *= 0.98;
|
||||
dot.vy *= 0.98;
|
||||
}
|
||||
|
||||
// Bounce off edges (accounting for radius)
|
||||
if (dot.x < dot.radius || dot.x > width - dot.radius) dot.vx *= -1;
|
||||
if (dot.y < dot.radius || dot.y > height - dot.radius) dot.vy *= -1;
|
||||
|
||||
// Clamp to bounds
|
||||
dot.x = Math.max(dot.radius, Math.min(width - dot.radius, dot.x));
|
||||
dot.y = Math.max(dot.radius, Math.min(height - dot.radius, dot.y));
|
||||
|
||||
const gradient = this.ctx.createRadialGradient(
|
||||
dot.x, dot.y, 0,
|
||||
dot.x, dot.y, dot.radius
|
||||
);
|
||||
gradient.addColorStop(0, dot.color + '40');
|
||||
gradient.addColorStop(1, 'transparent');
|
||||
|
||||
this.ctx.beginPath();
|
||||
this.ctx.arc(dot.x, dot.y, dot.radius, 0, Math.PI * 2);
|
||||
this.ctx.fillStyle = gradient;
|
||||
this.ctx.fill();
|
||||
}
|
||||
|
||||
this.animationId = requestAnimationFrame(this.animate);
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user