interactive-background fixes
All checks were successful
publish.yml / publish (push) Successful in 1m3s
All checks were successful
publish.yml / publish (push) Successful in 1m3s
This commit is contained in:
@@ -290,7 +290,14 @@ export class InteractiveBackground implements AfterViewInit, OnDestroy {
|
|||||||
private reducedMotion = false;
|
private reducedMotion = false;
|
||||||
private lowMotionMobile = false;
|
private lowMotionMobile = false;
|
||||||
private mediaQuery!: MediaQueryList;
|
private mediaQuery!: MediaQueryList;
|
||||||
|
private resizeFrameId: number | null = null;
|
||||||
|
private resizeSettleTimeout: ReturnType<typeof setTimeout> | null = null;
|
||||||
|
private lastCanvasWidth = 0;
|
||||||
|
private lastCanvasHeight = 0;
|
||||||
|
private lastCanvasDpr = 0;
|
||||||
private readonly pointerExcludeSelector = '[data-disable-bg-pointer]';
|
private readonly pointerExcludeSelector = '[data-disable-bg-pointer]';
|
||||||
|
private readonly resizeSettleDelayMs = 140;
|
||||||
|
private readonly resizeNoiseThresholdPx = 2;
|
||||||
|
|
||||||
ngAfterViewInit(): void {
|
ngAfterViewInit(): void {
|
||||||
const canvas = this.canvasRef.nativeElement;
|
const canvas = this.canvasRef.nativeElement;
|
||||||
@@ -306,14 +313,15 @@ export class InteractiveBackground implements AfterViewInit, OnDestroy {
|
|||||||
const mobileCount = Math.round(Math.min(54, Math.max(28, dynamicCount * 0.4)));
|
const mobileCount = Math.round(Math.min(54, Math.max(28, dynamicCount * 0.4)));
|
||||||
const particleCount = this.reducedMotion ? 42 : (this.lowMotionMobile ? mobileCount : dynamicCount);
|
const particleCount = this.reducedMotion ? 42 : (this.lowMotionMobile ? mobileCount : dynamicCount);
|
||||||
this.particleEngine = new ParticleEngine(context, particleCount);
|
this.particleEngine = new ParticleEngine(context, particleCount);
|
||||||
this.resizeCanvas();
|
this.resizeCanvas(true);
|
||||||
this.renderInitialFrame();
|
this.renderInitialFrame();
|
||||||
if (!this.reducedMotion) {
|
if (!this.reducedMotion) {
|
||||||
this.startAnimation();
|
this.startAnimation();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const onResize = () => this.resizeCanvas();
|
const visualViewport = window.visualViewport;
|
||||||
|
const onResize = () => this.scheduleCanvasResize();
|
||||||
const supportsPointerEvents = 'PointerEvent' in window;
|
const supportsPointerEvents = 'PointerEvent' in window;
|
||||||
const onPointerMove = (event: PointerEvent) => this.handlePointerMove(event);
|
const onPointerMove = (event: PointerEvent) => this.handlePointerMove(event);
|
||||||
const onPointerLeave = () => this.handlePointerLeave();
|
const onPointerLeave = () => this.handlePointerLeave();
|
||||||
@@ -333,7 +341,9 @@ export class InteractiveBackground implements AfterViewInit, OnDestroy {
|
|||||||
|
|
||||||
const onMotionChange = (event: MediaQueryListEvent) => this.handleMotionPreferenceChange(event.matches);
|
const onMotionChange = (event: MediaQueryListEvent) => this.handleMotionPreferenceChange(event.matches);
|
||||||
|
|
||||||
window.addEventListener('resize', onResize);
|
window.addEventListener('resize', onResize, { passive: true });
|
||||||
|
visualViewport?.addEventListener('resize', onResize, { passive: true });
|
||||||
|
visualViewport?.addEventListener('scroll', onResize, { passive: true });
|
||||||
|
|
||||||
if (supportsPointerEvents) {
|
if (supportsPointerEvents) {
|
||||||
window.addEventListener('pointermove', onPointerMove, { passive: true });
|
window.addEventListener('pointermove', onPointerMove, { passive: true });
|
||||||
@@ -356,6 +366,8 @@ export class InteractiveBackground implements AfterViewInit, OnDestroy {
|
|||||||
|
|
||||||
this.destroyCallbacks = [
|
this.destroyCallbacks = [
|
||||||
() => window.removeEventListener('resize', onResize),
|
() => window.removeEventListener('resize', onResize),
|
||||||
|
() => visualViewport?.removeEventListener('resize', onResize),
|
||||||
|
() => visualViewport?.removeEventListener('scroll', onResize),
|
||||||
() => window.removeEventListener('pointermove', onPointerMove),
|
() => window.removeEventListener('pointermove', onPointerMove),
|
||||||
() => window.removeEventListener('pointerleave', onPointerLeave),
|
() => window.removeEventListener('pointerleave', onPointerLeave),
|
||||||
() => window.removeEventListener('pointerdown', onPointerDown),
|
() => window.removeEventListener('pointerdown', onPointerDown),
|
||||||
@@ -375,22 +387,61 @@ export class InteractiveBackground implements AfterViewInit, OnDestroy {
|
|||||||
|
|
||||||
ngOnDestroy(): void {
|
ngOnDestroy(): void {
|
||||||
this.stopAnimation();
|
this.stopAnimation();
|
||||||
|
if (this.resizeFrameId !== null) {
|
||||||
|
cancelAnimationFrame(this.resizeFrameId);
|
||||||
|
this.resizeFrameId = null;
|
||||||
|
}
|
||||||
|
if (this.resizeSettleTimeout !== null) {
|
||||||
|
clearTimeout(this.resizeSettleTimeout);
|
||||||
|
this.resizeSettleTimeout = null;
|
||||||
|
}
|
||||||
for (const callback of this.destroyCallbacks) {
|
for (const callback of this.destroyCallbacks) {
|
||||||
callback();
|
callback();
|
||||||
}
|
}
|
||||||
this.destroyCallbacks = [];
|
this.destroyCallbacks = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
private resizeCanvas(): void {
|
private scheduleCanvasResize(): void {
|
||||||
|
if (this.resizeFrameId === null) {
|
||||||
|
this.resizeFrameId = requestAnimationFrame(() => {
|
||||||
|
this.resizeFrameId = null;
|
||||||
|
this.resizeCanvas(false);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.resizeSettleTimeout !== null) {
|
||||||
|
clearTimeout(this.resizeSettleTimeout);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.resizeSettleTimeout = setTimeout(() => {
|
||||||
|
this.resizeSettleTimeout = null;
|
||||||
|
this.resizeCanvas(true);
|
||||||
|
}, this.resizeSettleDelayMs);
|
||||||
|
}
|
||||||
|
|
||||||
|
private resizeCanvas(force: boolean): void {
|
||||||
if (!this.particleEngine) {
|
if (!this.particleEngine) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const canvas = this.canvasRef.nativeElement;
|
const canvas = this.canvasRef.nativeElement;
|
||||||
const width = window.innerWidth;
|
const viewport = this.getViewportSize();
|
||||||
const height = window.innerHeight;
|
const width = Math.max(1, Math.round(viewport.width));
|
||||||
|
const height = Math.max(1, Math.round(viewport.height));
|
||||||
const dpr = Math.min(window.devicePixelRatio || 1, 2);
|
const dpr = Math.min(window.devicePixelRatio || 1, 2);
|
||||||
|
|
||||||
|
const widthChanged = Math.abs(width - this.lastCanvasWidth) > this.resizeNoiseThresholdPx;
|
||||||
|
const heightChanged = Math.abs(height - this.lastCanvasHeight) > this.resizeNoiseThresholdPx;
|
||||||
|
const dprChanged = Math.abs(dpr - this.lastCanvasDpr) > 0.01;
|
||||||
|
|
||||||
|
if (!force && !widthChanged && !heightChanged && !dprChanged) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.lastCanvasWidth = width;
|
||||||
|
this.lastCanvasHeight = height;
|
||||||
|
this.lastCanvasDpr = dpr;
|
||||||
|
|
||||||
canvas.width = Math.floor(width * dpr);
|
canvas.width = Math.floor(width * dpr);
|
||||||
canvas.height = Math.floor(height * dpr);
|
canvas.height = Math.floor(height * dpr);
|
||||||
canvas.style.width = `${width}px`;
|
canvas.style.width = `${width}px`;
|
||||||
@@ -400,6 +451,21 @@ export class InteractiveBackground implements AfterViewInit, OnDestroy {
|
|||||||
this.renderInitialFrame();
|
this.renderInitialFrame();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private getViewportSize(): { width: number; height: number } {
|
||||||
|
const visualViewport = window.visualViewport;
|
||||||
|
if (visualViewport) {
|
||||||
|
return {
|
||||||
|
width: visualViewport.width,
|
||||||
|
height: visualViewport.height,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
width: window.innerWidth,
|
||||||
|
height: window.innerHeight,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
private startAnimation(): void {
|
private startAnimation(): void {
|
||||||
if (this.frameId !== null) {
|
if (this.frameId !== null) {
|
||||||
return;
|
return;
|
||||||
|
|||||||
Reference in New Issue
Block a user