From 02f936b34503305c75a2757f2aaf0f144424e02b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=BB=A8=ED=8A=B8=EB=A1=A4DELTA?= Date: Fri, 17 Apr 2026 11:38:34 +0900 Subject: [PATCH] Improve Brand Accent Renderer --- .../components/brand-accent/brand-accent.ts | 108 ++++++++---------- 1 file changed, 50 insertions(+), 58 deletions(-) diff --git a/src/app/components/brand-accent/brand-accent.ts b/src/app/components/brand-accent/brand-accent.ts index bf538d3..5eb350f 100644 --- a/src/app/components/brand-accent/brand-accent.ts +++ b/src/app/components/brand-accent/brand-accent.ts @@ -6,7 +6,7 @@ import { inject, NgZone, OnDestroy, - ViewChild, + viewChild, } from '@angular/core'; import { CommonModule } from '@angular/common'; import { SettingsService } from '../../services/settings.service'; @@ -19,7 +19,7 @@ import { SettingsService } from '../../services/settings.service'; styleUrl: './brand-accent.scss', }) export class BrandAccent implements OnDestroy { - @ViewChild('canvasElement') canvasRef!: ElementRef; + canvasRef = viewChild>('canvasElement'); private ctx!: CanvasRenderingContext2D; private settingsService = inject(SettingsService); @@ -27,8 +27,8 @@ export class BrandAccent implements OnDestroy { private bgGradient!: CanvasGradient; private gridGradient!: CanvasGradient; - private offscreenCanvas!: OffscreenCanvas; - private offscreenCtx!: OffscreenCanvasRenderingContext2D; + private readonly offscreenCanvas!: OffscreenCanvas; + private readonly offscreenCtx!: OffscreenCanvasRenderingContext2D; private resizeObserver?: ResizeObserver; private intersectionObserver?: IntersectionObserver; @@ -68,69 +68,69 @@ export class BrandAccent implements OnDestroy { }); afterNextRender(() => { - this.ctx = this.canvasRef.nativeElement.getContext('2d', { colorSpace: 'srgb' })!; + const nativeElement = this.canvasRef()?.nativeElement; - this.ngZone.runOutsideAngular(() => { - this.resizeObserver = new ResizeObserver((entries) => { - for (let entry of entries) { - const { inlineSize: width, blockSize: height } = entry.borderBoxSize[0]; - this.resize(width, height); - } - }); - this.resizeObserver.observe(this.canvasRef.nativeElement); + if (nativeElement) { + this.ctx = nativeElement.getContext('2d', { colorSpace: 'srgb' })!; - this.intersectionObserver = new IntersectionObserver( - (entries) => { - 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; + this.ngZone.runOutsideAngular(() => { + this.resizeObserver = new ResizeObserver((entries) => { + for (let entry of entries) { + const { inlineSize: width, blockSize: height } = entry.borderBoxSize[0]; + this.resize(width, height); } - }, - { rootMargin: '200px' }, - ); - this.intersectionObserver.observe(this.canvasRef.nativeElement); + }); + this.resizeObserver.observe(nativeElement); - // Safe Initialization - const rect = this.canvasRef.nativeElement.getBoundingClientRect(); - this.resize(rect.width, rect.height); + this.intersectionObserver = new IntersectionObserver( + (entries) => { + 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.animate(performance.now()); - }); + this.animation.startTime = performance.now(); + this.draw(); + // this.animate(performance.now()); + }); + } }); } private resize(width: number, height: number) { - const canvas = this.canvasRef.nativeElement; - const dpr = window.devicePixelRatio || 1; + const canvas = this.canvasRef()?.nativeElement; + if (canvas) { + const dpr = window.devicePixelRatio || 1; - this.rendererScale = dpr; + this.rendererScale = dpr; - canvas.width = width * dpr; - canvas.height = height * dpr; + canvas.width = width * dpr; + canvas.height = height * dpr; - if (this.offscreenCanvas) { - this.offscreenCanvas.width = width; - this.offscreenCanvas.height = height; + if (this.offscreenCanvas) { + this.offscreenCanvas.width = width; + 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() { - this.canvasNativeSize = [ - this.canvasRef.nativeElement.clientWidth, - this.canvasRef.nativeElement.clientHeight, - ]; - this.animation.gridDimension = [ Math.trunc(this.canvasNativeSize[0] / 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) => { 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.draw(); };