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>
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
import {
|
||||
Component, ChangeDetectionStrategy, input, inject,
|
||||
afterNextRender, DestroyRef, signal, effect,
|
||||
ElementRef, afterNextRender, DestroyRef, signal, effect,
|
||||
} from '@angular/core';
|
||||
import { ReducedMotionService } from '../../services/reduced-motion.service';
|
||||
|
||||
@@ -13,6 +13,7 @@ import { ReducedMotionService } from '../../services/reduced-motion.service';
|
||||
host: { 'class': 'fl-counter-rollup' },
|
||||
})
|
||||
export class FlCounterRollupComponent {
|
||||
private el = inject(ElementRef);
|
||||
private reducedMotion = inject(ReducedMotionService);
|
||||
private destroyRef = inject(DestroyRef);
|
||||
|
||||
@@ -32,24 +33,48 @@ export class FlCounterRollupComponent {
|
||||
private targetValue = 0;
|
||||
private startTime = 0;
|
||||
private animationFrame: number | null = null;
|
||||
private observer: IntersectionObserver | null = null;
|
||||
private hasAnimated = false;
|
||||
|
||||
constructor() {
|
||||
afterNextRender(() => {
|
||||
this.startAnimation();
|
||||
if (this.reducedMotion.reduced()) {
|
||||
this.targetValue = this.value();
|
||||
this.currentValue = this.targetValue;
|
||||
this.formattedValue.set(this.formatNumber(this.targetValue));
|
||||
return;
|
||||
}
|
||||
|
||||
this.targetValue = this.value();
|
||||
this.observer = new IntersectionObserver(
|
||||
(entries) => {
|
||||
if (entries[0]?.isIntersecting && !this.hasAnimated) {
|
||||
this.observer?.disconnect();
|
||||
this.observer = null;
|
||||
this.hasAnimated = true;
|
||||
this.startAnimation();
|
||||
}
|
||||
},
|
||||
{ threshold: 0.1 }
|
||||
);
|
||||
this.observer.observe(this.el.nativeElement);
|
||||
});
|
||||
|
||||
effect(() => {
|
||||
// React to value changes
|
||||
// React to value changes after initial animation
|
||||
const newValue = this.value();
|
||||
this.startValue = this.currentValue;
|
||||
this.targetValue = newValue;
|
||||
this.startAnimation();
|
||||
if (this.hasAnimated) {
|
||||
this.startAnimation();
|
||||
}
|
||||
});
|
||||
|
||||
this.destroyRef.onDestroy(() => {
|
||||
if (this.animationFrame !== null) {
|
||||
cancelAnimationFrame(this.animationFrame);
|
||||
}
|
||||
this.observer?.disconnect();
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user