This repository has been archived on 2026-06-18. You can view files and clone it, but cannot push or open issues or pull requests.
Files
flair-elements-ui/src/components/fl-ambient-light/fl-ambient-light.component.ts
Giuliano Silvestro 033cb7e10e fix: improve demo performance and fix broken component animations
- Add IntersectionObserver-based visibility pausing to AnimationLoopService
  (registerWithElement) so canvas components auto-pause when off-screen
- Fix voronoi-mesh: increase step size, add cell cache with invalidation
- Fix particle-field: replace O(n²) connections with spatial grid partitioning
- Fix ambient-light: remove GPU-killing blur filters, use inset -20% instead
- Tune demo: reduce particle/seed counts, remove always-on floating-layers
- Fix celebration components: move effect() inside afterNextRender so canvas
  is ready, pass [active]="true", render as fixed overlays instead of inline
- Fix one-shot text effects (typewriter, scramble-reveal, counter-rollup):
  defer animation until element is visible via IntersectionObserver

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 14:21:13 +10:00

100 lines
3.4 KiB
TypeScript

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';
@Component({
selector: 'fl-ambient-light',
standalone: true,
changeDetection: ChangeDetectionStrategy.OnPush,
template: `
<div class="fl-ambient-light__layer-1" [style.background]="layer1()"></div>
<div class="fl-ambient-light__layer-2" [style.background]="layer2()"></div>
<div class="fl-ambient-light__layer-3" [style.background]="layer3()"></div>
`,
styleUrl: './fl-ambient-light.component.scss',
host: {
'class': 'fl-ambient-light',
},
})
export class FlAmbientLightComponent {
private el = inject(ElementRef);
private reducedMotion = inject(ReducedMotionService);
private animationLoop = inject(AnimationLoopService);
private destroyRef = inject(DestroyRef);
// Inputs
readonly colors = input<string[]>(['#3b82f6', '#8b5cf6', '#ec4899']);
readonly speed = input(1);
readonly blur = input(100);
readonly opacity = input(0.5);
// Internal
private elapsed = 0;
private loopCleanup: (() => void) | null = null;
// Signals for layer backgrounds
readonly layer1 = signal('');
readonly layer2 = signal('');
readonly layer3 = signal('');
constructor() {
afterNextRender(() => {
if (this.reducedMotion.reduced()) {
this.setStaticGradients();
return;
}
const id = `ambient-light-${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 tick(delta: number): void {
this.elapsed += delta * this.speed();
this.updateGradients();
}
private updateGradients(): void {
const host = this.el.nativeElement as HTMLElement;
const width = host.offsetWidth;
const height = host.offsetHeight;
const colors = this.colors();
const blur = this.blur();
const opacity = this.opacity();
// Layer 1
const x1 = 50 + Math.sin(this.elapsed * 0.3) * 30;
const y1 = 50 + Math.cos(this.elapsed * 0.2) * 30;
const color1 = colors[0] || '#3b82f6';
this.layer1.set(`radial-gradient(circle at ${x1}% ${y1}%, ${color1} 0%, transparent ${blur}%)`);
// Layer 2
const x2 = 50 + Math.sin(this.elapsed * 0.4 + Math.PI) * 25;
const y2 = 50 + Math.cos(this.elapsed * 0.3 + Math.PI) * 25;
const color2 = colors[1] || '#8b5cf6';
this.layer2.set(`radial-gradient(circle at ${x2}% ${y2}%, ${color2} 0%, transparent ${blur}%)`);
// Layer 3
const x3 = 50 + Math.sin(this.elapsed * 0.5 + Math.PI * 0.5) * 20;
const y3 = 50 + Math.cos(this.elapsed * 0.4 + Math.PI * 0.5) * 20;
const color3 = colors[2] || '#ec4899';
this.layer3.set(`radial-gradient(circle at ${x3}% ${y3}%, ${color3} 0%, transparent ${blur}%)`);
}
private setStaticGradients(): void {
const colors = this.colors();
const blur = this.blur();
this.layer1.set(`radial-gradient(circle at 50% 50%, ${colors[0] || '#3b82f6'} 0%, transparent ${blur}%)`);
this.layer2.set(`radial-gradient(circle at 30% 70%, ${colors[1] || '#8b5cf6'} 0%, transparent ${blur}%)`);
this.layer3.set(`radial-gradient(circle at 70% 30%, ${colors[2] || '#ec4899'} 0%, transparent ${blur}%)`);
}
}