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