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, })) ); } }