diff --git a/src/app/components/brand-accent/brand-accent.html b/src/app/components/brand-accent/brand-accent.html new file mode 100644 index 0000000..b2f4116 --- /dev/null +++ b/src/app/components/brand-accent/brand-accent.html @@ -0,0 +1,2 @@ + + diff --git a/src/app/components/brand-accent/brand-accent.scss b/src/app/components/brand-accent/brand-accent.scss new file mode 100644 index 0000000..cb2f8cc --- /dev/null +++ b/src/app/components/brand-accent/brand-accent.scss @@ -0,0 +1,9 @@ +:host { + display: block; +} + +.canvas { + display: block; + width: 100%; + height: 100%; +} diff --git a/src/app/components/brand-accent/brand-accent.ts b/src/app/components/brand-accent/brand-accent.ts new file mode 100644 index 0000000..218a54a --- /dev/null +++ b/src/app/components/brand-accent/brand-accent.ts @@ -0,0 +1,130 @@ +import { afterNextRender, Component, ElementRef, NgZone, OnDestroy, ViewChild } from '@angular/core'; +import { CommonModule } from '@angular/common'; + +@Component({ + selector: 'brand-accent', + standalone: true, + imports: [CommonModule], + templateUrl: './brand-accent.html', + styleUrl: './brand-accent.scss', +}) +export class BrandAccent implements OnDestroy { + @ViewChild('canvasElement') canvasRef!: ElementRef; + private ctx!: CanvasRenderingContext2D; + private resizeObserver?: ResizeObserver; + + private canvasNativeSize: [number, number] = [0, 0]; + + private animation: { + id?: number; + startTime: number; + progress: number; + gridDimension: [number, number]; + + duration: number; + gridSquareSize: number; + } = { + startTime: 0, + progress: 0, + gridDimension: [0, 0], + + duration: 30, + gridSquareSize: 100, + }; + + constructor(private ngZone: NgZone) { + afterNextRender(() => { + this.ctx = this.canvasRef.nativeElement.getContext('2d')!; + + this.ngZone.runOutsideAngular(() => { + this.resizeObserver = new ResizeObserver(() => this.resize()); + this.resizeObserver.observe(this.canvasRef.nativeElement); + + this.resize(); // Safe Initialization + this.animation.startTime = performance.now(); + this.draw(); + + this.animate(performance.now()); + }); + }); + } + + private resize() { + const canvas = this.canvasRef.nativeElement; + const dpr = window.devicePixelRatio || 1; + const rect = canvas.getBoundingClientRect(); + + canvas.width = rect.width * dpr; + canvas.height = rect.height * dpr; + + this.ctx.scale(dpr, dpr); + this.updateAnimationInfo(); + } + + 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, + ]; + } + + private animate = (currentTime: number) => { + this.animation.id = requestAnimationFrame(this.animate); + + this.update(currentTime); + this.draw(); + }; + + private update(currentTime: number) { + let elapsedTime: number; + elapsedTime = (currentTime - this.animation.startTime) / 1000; + + const animationTime = elapsedTime % this.animation.duration; + this.animation.progress = animationTime / this.animation.duration; + } + + private draw() { + this.ctx.clearRect(0, 0, this.canvasNativeSize[0], this.canvasNativeSize[1]); + const linearGradient = this.ctx.createLinearGradient(0, 0, this.canvasNativeSize[0], this.canvasNativeSize[1]); + + linearGradient.addColorStop(0, 'oklch(1 0 0 / 0.6)'); + linearGradient.addColorStop(1, 'oklch(0.7938 0.1292 47.77 / 0.6)'); + + this.ctx.fillStyle = linearGradient; + this.ctx.fillRect(0, 0, this.canvasNativeSize[0], this.canvasNativeSize[1]); + + this.ctx.globalCompositeOperation = 'multiply'; + + this.ctx.beginPath(); + this.ctx.lineWidth = 2; + this.ctx.strokeStyle = 'rgba(0, 0, 0, 0.2)'; + this.ctx.lineCap = 'round'; + + const lineRenderMargin = this.animation.gridSquareSize * 2; + + const xOffset = (this.canvasNativeSize[0] - this.animation.gridDimension[0] * this.animation.gridSquareSize) / 2; + const yOffset = (this.canvasNativeSize[1] - this.animation.gridDimension[1] * this.animation.gridSquareSize) / 2; + const movingOffset = -(this.animation.gridSquareSize * this.animation.progress); + + for (let y = 0; y < this.animation.gridDimension[1]; y++) { + this.ctx.moveTo(-lineRenderMargin, y * this.animation.gridSquareSize + yOffset + movingOffset); + this.ctx.lineTo(this.canvasNativeSize[0] + lineRenderMargin, y * this.animation.gridSquareSize + yOffset + movingOffset); + } + + for (let x = 0; x < this.animation.gridDimension[0]; x++) { + this.ctx.moveTo(x * this.animation.gridSquareSize + xOffset + movingOffset, -lineRenderMargin); + this.ctx.lineTo(x * this.animation.gridSquareSize + xOffset + movingOffset, this.canvasNativeSize[1] + lineRenderMargin); + } + + this.ctx.stroke() + } + + ngOnDestroy() { + if (this.animation.id) { + cancelAnimationFrame(this.animation.id); + } + this.resizeObserver?.disconnect(); + } +} diff --git a/src/app/pages/main/main.html b/src/app/pages/main/main.html index dfb081a..0048483 100644 --- a/src/app/pages/main/main.html +++ b/src/app/pages/main/main.html @@ -1,4 +1,6 @@
+ +

AEF Component Test

diff --git a/src/app/pages/main/main.scss b/src/app/pages/main/main.scss index 966ee6a..5fd92d9 100644 --- a/src/app/pages/main/main.scss +++ b/src/app/pages/main/main.scss @@ -7,3 +7,15 @@ section#dropdown-test { .test-container { max-width: 100%; } + +brand-accent { + position: absolute; + + width: 100%; + height: 100%; + + top: 0; + left: 0; + + z-index: -999999; +} diff --git a/src/app/pages/main/main.ts b/src/app/pages/main/main.ts index 8f6e8b4..36fb22b 100644 --- a/src/app/pages/main/main.ts +++ b/src/app/pages/main/main.ts @@ -1,11 +1,12 @@ import { Component, signal } from '@angular/core'; import { Switch } from '../../components/switch/switch'; import { Dropdown, DropdownOption } from '../../components/dropdown/dropdown'; +import { BrandAccent } from '../../components/brand-accent/brand-accent'; @Component({ selector: 'app-main-page', standalone: true, - imports: [Switch, Dropdown], + imports: [Switch, Dropdown, BrandAccent], templateUrl: './main.html', styleUrl: './main.scss', }) diff --git a/src/styles.scss b/src/styles.scss index 417c5f8..4e84fd0 100644 --- a/src/styles.scss +++ b/src/styles.scss @@ -66,6 +66,10 @@ section { padding: 20px; border: 2px solid oklch(0 0 0 / 20%); // rgba(0, 0, 0, 0.2) border-radius: 8px; + + background-color: oklch(1 0 0 / 60%); + + backdrop-filter: blur(20px); } .container {