Improve Brand Accent Renderer

This commit is contained in:
copecone committed 2026-04-17 11:38:34 +09:00
1 parent 0801ed3c70
commit 02f936b345
1 file changed
+50 -58
+50 -58
View File
@@ -6,7 +6,7 @@ import {
inject, inject,
NgZone, NgZone,
OnDestroy, OnDestroy,
ViewChild, viewChild,
} from '@angular/core'; } from '@angular/core';
import { CommonModule } from '@angular/common'; import { CommonModule } from '@angular/common';
import { SettingsService } from '../../services/settings.service'; import { SettingsService } from '../../services/settings.service';
@@ -19,7 +19,7 @@ import { SettingsService } from '../../services/settings.service';
styleUrl: './brand-accent.scss', styleUrl: './brand-accent.scss',
}) })
export class BrandAccent implements OnDestroy { export class BrandAccent implements OnDestroy {
@ViewChild('canvasElement') canvasRef!: ElementRef<HTMLCanvasElement>; canvasRef = viewChild<ElementRef<HTMLCanvasElement>>('canvasElement');
private ctx!: CanvasRenderingContext2D; private ctx!: CanvasRenderingContext2D;
private settingsService = inject(SettingsService); private settingsService = inject(SettingsService);
@@ -27,8 +27,8 @@ export class BrandAccent implements OnDestroy {
private bgGradient!: CanvasGradient; private bgGradient!: CanvasGradient;
private gridGradient!: CanvasGradient; private gridGradient!: CanvasGradient;
private offscreenCanvas!: OffscreenCanvas; private readonly offscreenCanvas!: OffscreenCanvas;
private offscreenCtx!: OffscreenCanvasRenderingContext2D; private readonly offscreenCtx!: OffscreenCanvasRenderingContext2D;
private resizeObserver?: ResizeObserver; private resizeObserver?: ResizeObserver;
private intersectionObserver?: IntersectionObserver; private intersectionObserver?: IntersectionObserver;
@@ -68,69 +68,69 @@ export class BrandAccent implements OnDestroy {
}); });
afterNextRender(() => { afterNextRender(() => {
this.ctx = this.canvasRef.nativeElement.getContext('2d', { colorSpace: 'srgb' })!; const nativeElement = this.canvasRef()?.nativeElement;
this.ngZone.runOutsideAngular(() => { if (nativeElement) {
this.resizeObserver = new ResizeObserver((entries) => { this.ctx = nativeElement.getContext('2d', { colorSpace: 'srgb' })!;
for (let entry of entries) {
const { inlineSize: width, blockSize: height } = entry.borderBoxSize[0];
this.resize(width, height);
}
});
this.resizeObserver.observe(this.canvasRef.nativeElement);
this.intersectionObserver = new IntersectionObserver( this.ngZone.runOutsideAngular(() => {
(entries) => { this.resizeObserver = new ResizeObserver((entries) => {
if (entries[0].isIntersecting) { for (let entry of entries) {
if (!this.animation.id) this.animate(performance.now()); const { inlineSize: width, blockSize: height } = entry.borderBoxSize[0];
} else if (this.animation.id) { this.resize(width, height);
cancelAnimationFrame(this.animation.id);
this.animation.id = undefined;
} }
}, });
{ rootMargin: '200px' }, this.resizeObserver.observe(nativeElement);
);
this.intersectionObserver.observe(this.canvasRef.nativeElement);
// Safe Initialization this.intersectionObserver = new IntersectionObserver(
const rect = this.canvasRef.nativeElement.getBoundingClientRect(); (entries) => {
this.resize(rect.width, rect.height); if (entries[0].isIntersecting) {
if (!this.animation.id) this.animate(performance.now());
} else if (this.animation.id) {
cancelAnimationFrame(this.animation.id);
this.animation.id = undefined;
}
},
{ rootMargin: '200px' },
);
this.intersectionObserver.observe(nativeElement);
this.animation.startTime = performance.now(); // Safe Initialization
const rect = nativeElement.getBoundingClientRect();
this.resize(rect.width, rect.height);
this.draw(); this.animation.startTime = performance.now();
// this.animate(performance.now()); this.draw();
}); // this.animate(performance.now());
});
}
}); });
} }
private resize(width: number, height: number) { private resize(width: number, height: number) {
const canvas = this.canvasRef.nativeElement; const canvas = this.canvasRef()?.nativeElement;
const dpr = window.devicePixelRatio || 1; if (canvas) {
const dpr = window.devicePixelRatio || 1;
this.rendererScale = dpr; this.rendererScale = dpr;
canvas.width = width * dpr; canvas.width = width * dpr;
canvas.height = height * dpr; canvas.height = height * dpr;
if (this.offscreenCanvas) { if (this.offscreenCanvas) {
this.offscreenCanvas.width = width; this.offscreenCanvas.width = width;
this.offscreenCanvas.height = height; this.offscreenCanvas.height = height;
}
this.canvasNativeSize = [width, height];
this.ctx.scale(dpr, dpr);
this.updateAnimationInfo();
this.animate(performance.now());
} }
this.ctx.scale(dpr, dpr);
this.updateAnimationInfo();
this.update(performance.now());
this.draw();
} }
private updateAnimationInfo() { private updateAnimationInfo() {
this.canvasNativeSize = [
this.canvasRef.nativeElement.clientWidth,
this.canvasRef.nativeElement.clientHeight,
];
this.animation.gridDimension = [ this.animation.gridDimension = [
Math.trunc(this.canvasNativeSize[0] / this.animation.gridSquareSize) + 3, Math.trunc(this.canvasNativeSize[0] / this.animation.gridSquareSize) + 3,
Math.trunc(this.canvasNativeSize[1] / this.animation.gridSquareSize) + 3, Math.trunc(this.canvasNativeSize[1] / this.animation.gridSquareSize) + 3,
@@ -209,14 +209,6 @@ export class BrandAccent implements OnDestroy {
private animate = (currentTime: number) => { private animate = (currentTime: number) => {
this.animation.id = requestAnimationFrame(this.animate); this.animation.id = requestAnimationFrame(this.animate);
const canvas = this.canvasRef.nativeElement;
if (
canvas.clientWidth !== this.canvasNativeSize[0] ||
canvas.clientHeight !== this.canvasNativeSize[1]
) {
return;
}
this.update(currentTime); this.update(currentTime);
this.draw(); this.draw();
}; };