From 4f17b07f70fbfa5223169e4dba38ce969998c862 Mon Sep 17 00:00:00 2001 From: AdamBtech <60339324+AdamBtech@users.noreply.github.com> Date: Fri, 23 May 2025 19:33:22 +0200 Subject: [PATCH] Added scramble animation on mount of section title component --- .../section-title.component.html | 42 ++++--- .../section-title/section-title.component.ts | 106 +++++++++++++++++- 2 files changed, 129 insertions(+), 19 deletions(-) diff --git a/src/app/shared/ui/section-title/section-title.component.html b/src/app/shared/ui/section-title/section-title.component.html index a6198e2..fd9413c 100644 --- a/src/app/shared/ui/section-title/section-title.component.html +++ b/src/app/shared/ui/section-title/section-title.component.html @@ -1,19 +1,27 @@

-

- {{title()}} -

- -

- {{title()}} -

- -

- {{title()}} -

+ +

+ {{title()}} +

+ + +

+ {{title()}} +

+ + +

+ {{title()}} +

diff --git a/src/app/shared/ui/section-title/section-title.component.ts b/src/app/shared/ui/section-title/section-title.component.ts index c0786cc..a693e39 100644 --- a/src/app/shared/ui/section-title/section-title.component.ts +++ b/src/app/shared/ui/section-title/section-title.component.ts @@ -1,4 +1,11 @@ -import { Component, input } from '@angular/core'; +import { + Component, + input, + OnInit, + OnDestroy, + ElementRef, + ViewChild, +} from '@angular/core'; @Component({ selector: 'app-section-title', @@ -6,6 +13,101 @@ import { Component, input } from '@angular/core'; templateUrl: './section-title.component.html', styleUrl: './section-title.component.css', }) -export class SectionTitleComponent { +export class SectionTitleComponent implements OnInit, OnDestroy { + @ViewChild('mainText', { static: true }) + mainTextRef!: ElementRef; + @ViewChild('shadow1', { static: true }) shadow1Ref!: ElementRef; + @ViewChild('shadow2', { static: true }) shadow2Ref!: ElementRef; + title = input(); + + private animationFrame?: number; + private timeoutIds: number[] = []; + + ngOnInit() { + // Start animation after component initializes + setTimeout(() => this.startScrambleAnimation(), 100); + } + + ngOnDestroy() { + if (this.animationFrame) { + cancelAnimationFrame(this.animationFrame); + } + this.timeoutIds.forEach((id) => clearTimeout(id)); + } + + private startScrambleAnimation() { + const titleText = this.title() || ''; + if (!titleText) return; + + const duration = 2000; // Total animation duration + const letterDelay = 80; // Delay between each letter reveal + + this.simultaneousScrambleAndReveal(titleText, duration, letterDelay); + } + + private simultaneousScrambleAndReveal( + targetText: string, + totalDuration: number, + letterDelay: number, + ) { + const startTime = Date.now(); + const scrambleChars = '01'; + const revealedLetters = new Array(targetText.length).fill(false); + const letterBinaryStates = new Array(targetText.length) + .fill(null) + .map(() => (Math.random() > 0.5 ? '0' : '1')); + + // Schedule letter reveals + for (let i = 0; i < targetText.length; i++) { + if (targetText[i] !== ' ') { + const timeoutId = setTimeout(() => { + revealedLetters[i] = true; + }, i * letterDelay); + this.timeoutIds.push(timeoutId); + } else { + revealedLetters[i] = true; // Spaces are always "revealed" + } + } + + const animate = () => { + const elapsed = Date.now() - startTime; + + if (elapsed < totalDuration) { + let displayText = ''; + + for (let i = 0; i < targetText.length; i++) { + if (targetText[i] === ' ') { + displayText += ' '; + } else if (revealedLetters[i]) { + displayText += targetText[i]; + } else { + // Each position gets its own random binary that changes + letterBinaryStates[i] = Math.random() > 0.5 ? '0' : '1'; + displayText += letterBinaryStates[i]; + } + } + + this.updateTextContent(displayText); + this.animationFrame = requestAnimationFrame(animate); + } else { + // Final state - show complete text + this.updateTextContent(targetText); + } + }; + + animate(); + } + + private updateTextContent(text: string) { + if (this.mainTextRef?.nativeElement) { + this.mainTextRef.nativeElement.textContent = text; + } + if (this.shadow1Ref?.nativeElement) { + this.shadow1Ref.nativeElement.textContent = text; + } + if (this.shadow2Ref?.nativeElement) { + this.shadow2Ref.nativeElement.textContent = text; + } + } }