import {
Component, ChangeDetectionStrategy, input, inject,
ElementRef, afterNextRender, DestroyRef, signal,
} from '@angular/core';
import { ReducedMotionService } from '../../services/reduced-motion.service';
import { AnimationLoopService } from '../../services/animation-loop.service';
interface MeshPoint {
x: number;
y: number;
vx: number;
vy: number;
color: string;
}
@Component({
selector: 'fl-gradient-mesh',
standalone: true,
changeDetection: ChangeDetectionStrategy.OnPush,
template: `
@for (point of meshPoints(); track $index) {
}
`,
styleUrl: './fl-gradient-mesh.component.scss',
host: {
'class': 'fl-gradient-mesh',
},
})
export class FlGradientMeshComponent {
private el = inject(ElementRef);
private reducedMotion = inject(ReducedMotionService);
private animationLoop = inject(AnimationLoopService);
private destroyRef = inject(DestroyRef);
// Inputs
readonly points = input(5);
readonly colors = input(['#3b82f6', '#8b5cf6', '#ec4899', '#f59e0b']);
readonly speed = input(1);
readonly blur = input(80);
// Internal
private meshPointsData: MeshPoint[] = [];
private loopCleanup: (() => void) | null = null;
// Signals
readonly meshPoints = signal>([]);
constructor() {
afterNextRender(() => {
this.initializeMesh();
if (this.reducedMotion.reduced()) {
this.updateDisplay();
return;
}
const id = `gradient-mesh-${Math.random().toString(36).slice(2)}`;
this.loopCleanup = this.animationLoop.registerWithElement(id, this.el.nativeElement, (delta) => this.tick(delta));
});
this.destroyRef.onDestroy(() => {
this.loopCleanup?.();
});
}
private initializeMesh(): void {
const count = this.points();
const colors = this.colors();
this.meshPointsData = [];
for (let i = 0; i < count; i++) {
this.meshPointsData.push({
x: Math.random() * 100,
y: Math.random() * 100,
vx: (Math.random() - 0.5) * 10,
vy: (Math.random() - 0.5) * 10,
color: colors[i % colors.length],
});
}
this.updateDisplay();
}
private tick(delta: number): void {
const speed = this.speed() * delta * 10;
for (const point of this.meshPointsData) {
point.x += point.vx * speed;
point.y += point.vy * speed;
// Bounce off edges
if (point.x < 0 || point.x > 100) {
point.vx *= -1;
point.x = Math.max(0, Math.min(100, point.x));
}
if (point.y < 0 || point.y > 100) {
point.vy *= -1;
point.y = Math.max(0, Math.min(100, point.y));
}
}
this.updateDisplay();
}
private updateDisplay(): void {
this.meshPoints.set(
this.meshPointsData.map(p => ({
x: p.x,
y: p.y,
color: p.color,
}))
);
}
}