import { Component, ElementRef, inject, NgZone, OnInit } from '@angular/core' import { WA_WINDOW } from '@ng-web-apis/common' // a higher fade factor will make the characters fade quicker const FADE_FACTOR = 0.07 @Component({ selector: 'canvas[matrix]', template: 'Your browser does not support the canvas element.', styles: ':host { position: fixed; top: 0 }', }) export class MatrixComponent implements OnInit { private readonly ngZone = inject(NgZone) private readonly window = inject(WA_WINDOW) private readonly el: HTMLCanvasElement = inject(ElementRef).nativeElement private readonly ctx = this.el.getContext('2d')! private tileSize = 16 private columns: any[] = [] private maxStackHeight = 0 ngOnInit() { this.ngZone.runOutsideAngular(() => { this.setupMatrixGrid() this.tick() }) } private setupMatrixGrid() { this.el.width = Math.max(this.window.innerWidth, 1920) this.el.height = Math.max(this.window.innerHeight, 1080) this.maxStackHeight = Math.ceil(this.ctx.canvas.height / this.tileSize) // divide the canvas into columns for (let i = 0; i < this.ctx.canvas.width / this.tileSize; ++i) { const column = {} as any // save the x position of the column column.x = i * this.tileSize // create a random stack height for the column column.stackHeight = 10 + Math.random() * this.maxStackHeight // add a counter to count the stack height column.stackCounter = 0 // add the column to the list this.columns.push(column) } } private draw() { // draw a semi transparent black rectangle on top of the scene to slowly fade older characters this.ctx.fillStyle = `rgba(0, 0, 0, ${FADE_FACTOR})` this.ctx.fillRect(0, 0, this.ctx.canvas.width, this.ctx.canvas.height) // pick a font slightly smaller than the tile size this.ctx.font = `${this.tileSize - 2}px monospace` this.ctx.fillStyle = '#ff4961' for (let i = 0; i < this.columns.length; ++i) { // pick a random ascii character (change the 94 to a higher number to include more characters) const { x, stackCounter } = this.columns[i] const char = String.fromCharCode(33 + Math.floor(Math.random() * 94)) this.ctx.fillText(char, x, stackCounter * this.tileSize + this.tileSize) // if the stack is at its height limit, pick a new random height and reset the counter if (++this.columns[i].stackCounter >= this.columns[i].stackHeight) { this.columns[i].stackHeight = 10 + Math.random() * this.maxStackHeight this.columns[i].stackCounter = 0 } } } private tick() { this.draw() setTimeout(this.tick.bind(this), 50) } }