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'; import { generateBlobPath } from '../../utils/svg.utils'; @Component({ selector: 'fl-blob', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, template: ` @for (color of colors(); track $index) { } `, styleUrl: './fl-blob.component.scss', host: { 'class': 'fl-blob', }, }) export class FlBlobComponent { private el = inject(ElementRef); private reducedMotion = inject(ReducedMotionService); private animationLoop = inject(AnimationLoopService); private destroyRef = inject(DestroyRef); // Inputs readonly colors = input(['#3b82f6', '#8b5cf6']); readonly speed = input(1); readonly complexity = input(8); readonly size = input(200); // Internal private elapsed = 0; private loopCleanup: (() => void) | null = null; protected readonly id = `blob-${Math.random().toString(36).slice(2)}`; // Signals readonly blobPath = signal(''); readonly viewBox = signal('0 0 200 200'); constructor() { afterNextRender(() => { this.viewBox.set(`0 0 ${this.size()} ${this.size()}`); if (this.reducedMotion.reduced()) { this.updateBlob(0); return; } const id = `blob-${Math.random().toString(36).slice(2)}`; this.loopCleanup = this.animationLoop.register(id, (delta) => this.tick(delta)); }); this.destroyRef.onDestroy(() => { this.loopCleanup?.(); }); } private tick(delta: number): void { this.elapsed += delta * this.speed(); this.updateBlob(this.elapsed); } private updateBlob(phase: number): void { const size = this.size(); const cx = size / 2; const cy = size / 2; const radius = size * 0.35; const variance = size * 0.1; const points = this.complexity(); const path = generateBlobPath(cx, cy, radius, points, variance, phase); this.blobPath.set(path); } }