fix(ssr): skip canvas/WebGL components on the server
continuous-integration/drone/push Build is passing
continuous-integration/drone/push Build is passing
requestAnimationFrame/document are browser-only; Three.js trophies, particles, cursor, and loading screen now no-op during SSR. Add a Node rAF shim as a safety net.
This commit is contained in:
1 parent
0a30980105
commit
1583b7035f
14 files changed
+106
-8
No files matched your search
@@ -1,5 +1,17 @@
|
|||||||
import 'dotenv/config';
|
import 'dotenv/config';
|
||||||
import 'zone.js/node';
|
import 'zone.js/node';
|
||||||
|
|
||||||
|
// Node has no rAF; polyfill before Angular SSR bootstraps (belt + suspenders
|
||||||
|
// alongside browser-only guards in canvas components).
|
||||||
|
if (typeof globalThis.requestAnimationFrame !== 'function') {
|
||||||
|
globalThis.requestAnimationFrame = ((cb: FrameRequestCallback) =>
|
||||||
|
setTimeout(() => cb(Date.now()), 16)) as unknown as typeof requestAnimationFrame;
|
||||||
|
}
|
||||||
|
if (typeof globalThis.cancelAnimationFrame !== 'function') {
|
||||||
|
globalThis.cancelAnimationFrame = ((id: number) =>
|
||||||
|
clearTimeout(id)) as unknown as typeof cancelAnimationFrame;
|
||||||
|
}
|
||||||
|
|
||||||
import { APP_BASE_HREF } from '@angular/common';
|
import { APP_BASE_HREF } from '@angular/common';
|
||||||
import { CommonEngine } from '@angular/ssr/node';
|
import { CommonEngine } from '@angular/ssr/node';
|
||||||
import express from 'express';
|
import express from 'express';
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import { LoadingScreenComponent } from './shared/components/loading-screen/loadi
|
|||||||
import { NavbarComponent } from './shared/components/navbar/navbar.component';
|
import { NavbarComponent } from './shared/components/navbar/navbar.component';
|
||||||
import { FooterComponent } from './shared/components/footer/footer.component';
|
import { FooterComponent } from './shared/components/footer/footer.component';
|
||||||
import { GoalsService } from './core/services/goals.service';
|
import { GoalsService } from './core/services/goals.service';
|
||||||
|
import { injectIsBrowser } from './shared/utils/is-browser';
|
||||||
import { filter, map, startWith } from 'rxjs/operators';
|
import { filter, map, startWith } from 'rxjs/operators';
|
||||||
import { toSignal } from '@angular/core/rxjs-interop';
|
import { toSignal } from '@angular/core/rxjs-interop';
|
||||||
|
|
||||||
@@ -21,17 +22,21 @@ import { toSignal } from '@angular/core/rxjs-interop';
|
|||||||
FooterComponent,
|
FooterComponent,
|
||||||
],
|
],
|
||||||
template: `
|
template: `
|
||||||
<app-loading-screen
|
@if (isBrowser) {
|
||||||
[progress]="loadingProgress"
|
<app-loading-screen
|
||||||
[loadingText]="loadingText"
|
[progress]="loadingProgress"
|
||||||
[fadeOut]="loadingComplete"
|
[loadingText]="loadingText"
|
||||||
></app-loading-screen>
|
[fadeOut]="loadingComplete"
|
||||||
<app-custom-cursor></app-custom-cursor>
|
></app-loading-screen>
|
||||||
|
<app-custom-cursor></app-custom-cursor>
|
||||||
|
}
|
||||||
<div
|
<div
|
||||||
class="min-h-screen bg-gradient-to-br from-gray-50 via-blue-50 to-purple-50 relative overflow-hidden transition-opacity duration-500 flex flex-col"
|
class="min-h-screen bg-gradient-to-br from-gray-50 via-blue-50 to-purple-50 relative overflow-hidden transition-opacity duration-500 flex flex-col"
|
||||||
[class.opacity-0]="!loadingComplete()"
|
[class.opacity-0]="isBrowser && !loadingComplete()"
|
||||||
>
|
>
|
||||||
<app-particle-background></app-particle-background>
|
@if (isBrowser) {
|
||||||
|
<app-particle-background></app-particle-background>
|
||||||
|
}
|
||||||
<div class="relative z-10 flex flex-col min-h-screen">
|
<div class="relative z-10 flex flex-col min-h-screen">
|
||||||
@if (showNavbarAndFooter()) {
|
@if (showNavbarAndFooter()) {
|
||||||
<app-navbar></app-navbar>
|
<app-navbar></app-navbar>
|
||||||
@@ -49,6 +54,7 @@ import { toSignal } from '@angular/core/rxjs-interop';
|
|||||||
})
|
})
|
||||||
export class AppComponent implements OnInit {
|
export class AppComponent implements OnInit {
|
||||||
title = 'goals-tracker';
|
title = 'goals-tracker';
|
||||||
|
readonly isBrowser = injectIsBrowser();
|
||||||
loadingProgress = signal(0);
|
loadingProgress = signal(0);
|
||||||
loadingText = signal('Initializing...');
|
loadingText = signal('Initializing...');
|
||||||
loadingComplete = signal(false);
|
loadingComplete = signal(false);
|
||||||
@@ -96,6 +102,12 @@ export class AppComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
|
if (!this.isBrowser) {
|
||||||
|
// SSR: skip canvas/loading chrome; render content immediately.
|
||||||
|
this.loadingComplete.set(true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
this.loadAllData();
|
this.loadAllData();
|
||||||
|
|
||||||
// Track route changes for navigation loading
|
// Track route changes for navigation loading
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { Component, Input, OnInit, OnDestroy } from '@angular/core';
|
import { Component, Input, OnInit, OnDestroy } from '@angular/core';
|
||||||
import { CommonModule } from '@angular/common';
|
import { CommonModule } from '@angular/common';
|
||||||
|
import { injectIsBrowser } from '../../utils/is-browser';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-animated-counter',
|
selector: 'app-animated-counter',
|
||||||
@@ -13,6 +14,8 @@ import { CommonModule } from '@angular/common';
|
|||||||
styles: [],
|
styles: [],
|
||||||
})
|
})
|
||||||
export class AnimatedCounterComponent implements OnInit, OnDestroy {
|
export class AnimatedCounterComponent implements OnInit, OnDestroy {
|
||||||
|
private readonly isBrowser = injectIsBrowser();
|
||||||
|
|
||||||
@Input() value: number = 0;
|
@Input() value: number = 0;
|
||||||
@Input() duration: number = 2000;
|
@Input() duration: number = 2000;
|
||||||
@Input() decimals: number = 1;
|
@Input() decimals: number = 1;
|
||||||
@@ -25,6 +28,10 @@ export class AnimatedCounterComponent implements OnInit, OnDestroy {
|
|||||||
private startValue: number = 0;
|
private startValue: number = 0;
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
|
if (!this.isBrowser) {
|
||||||
|
this.displayValue = this.value;
|
||||||
|
return;
|
||||||
|
}
|
||||||
this.startValue = this.displayValue;
|
this.startValue = this.displayValue;
|
||||||
this.startTime = performance.now();
|
this.startTime = performance.now();
|
||||||
this.animate();
|
this.animate();
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import {
|
|||||||
AfterViewInit,
|
AfterViewInit,
|
||||||
} from '@angular/core';
|
} from '@angular/core';
|
||||||
import { CommonModule } from '@angular/common';
|
import { CommonModule } from '@angular/common';
|
||||||
|
import { injectIsBrowser } from '../../utils/is-browser';
|
||||||
import * as THREE from 'three';
|
import * as THREE from 'three';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
@@ -51,6 +52,8 @@ import * as THREE from 'three';
|
|||||||
],
|
],
|
||||||
})
|
})
|
||||||
export class BasketballGameComponent implements OnInit, AfterViewInit, OnDestroy {
|
export class BasketballGameComponent implements OnInit, AfterViewInit, OnDestroy {
|
||||||
|
private readonly isBrowser = injectIsBrowser();
|
||||||
|
|
||||||
@ViewChild('canvas', { static: false }) canvasRef!: ElementRef<HTMLCanvasElement>;
|
@ViewChild('canvas', { static: false }) canvasRef!: ElementRef<HTMLCanvasElement>;
|
||||||
|
|
||||||
private scene!: THREE.Scene;
|
private scene!: THREE.Scene;
|
||||||
@@ -77,6 +80,9 @@ export class BasketballGameComponent implements OnInit, AfterViewInit, OnDestroy
|
|||||||
ngOnInit(): void {}
|
ngOnInit(): void {}
|
||||||
|
|
||||||
ngAfterViewInit(): void {
|
ngAfterViewInit(): void {
|
||||||
|
if (!this.isBrowser) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
this.initThree();
|
this.initThree();
|
||||||
this.createScene();
|
this.createScene();
|
||||||
this.setupEventListeners();
|
this.setupEventListeners();
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { Component, OnInit, OnDestroy, ElementRef, ViewChild, AfterViewInit } from '@angular/core';
|
import { Component, OnInit, OnDestroy, ElementRef, ViewChild, AfterViewInit } from '@angular/core';
|
||||||
import { CommonModule } from '@angular/common';
|
import { CommonModule } from '@angular/common';
|
||||||
|
import { injectIsBrowser } from '../../utils/is-browser';
|
||||||
|
|
||||||
interface ConfettiParticle {
|
interface ConfettiParticle {
|
||||||
x: number;
|
x: number;
|
||||||
@@ -28,6 +29,8 @@ interface ConfettiParticle {
|
|||||||
],
|
],
|
||||||
})
|
})
|
||||||
export class ConfettiComponent implements OnInit, AfterViewInit, OnDestroy {
|
export class ConfettiComponent implements OnInit, AfterViewInit, OnDestroy {
|
||||||
|
private readonly isBrowser = injectIsBrowser();
|
||||||
|
|
||||||
@ViewChild('canvas', { static: false }) canvasRef!: ElementRef<HTMLCanvasElement>;
|
@ViewChild('canvas', { static: false }) canvasRef!: ElementRef<HTMLCanvasElement>;
|
||||||
|
|
||||||
private ctx!: CanvasRenderingContext2D;
|
private ctx!: CanvasRenderingContext2D;
|
||||||
@@ -38,6 +41,9 @@ export class ConfettiComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||||||
ngOnInit(): void {}
|
ngOnInit(): void {}
|
||||||
|
|
||||||
ngAfterViewInit(): void {
|
ngAfterViewInit(): void {
|
||||||
|
if (!this.isBrowser) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
const canvas = this.canvasRef.nativeElement;
|
const canvas = this.canvasRef.nativeElement;
|
||||||
this.ctx = canvas.getContext('2d')!;
|
this.ctx = canvas.getContext('2d')!;
|
||||||
canvas.width = window.innerWidth;
|
canvas.width = window.innerWidth;
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import {
|
|||||||
signal,
|
signal,
|
||||||
} from '@angular/core';
|
} from '@angular/core';
|
||||||
import { CommonModule } from '@angular/common';
|
import { CommonModule } from '@angular/common';
|
||||||
|
import { injectIsBrowser } from '../../utils/is-browser';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-custom-cursor',
|
selector: 'app-custom-cursor',
|
||||||
@@ -123,6 +124,8 @@ import { CommonModule } from '@angular/common';
|
|||||||
],
|
],
|
||||||
})
|
})
|
||||||
export class CustomCursorComponent implements OnInit, OnDestroy {
|
export class CustomCursorComponent implements OnInit, OnDestroy {
|
||||||
|
private readonly isBrowser = injectIsBrowser();
|
||||||
|
|
||||||
x = 0;
|
x = 0;
|
||||||
y = 0;
|
y = 0;
|
||||||
ringX = 0;
|
ringX = 0;
|
||||||
@@ -145,6 +148,9 @@ export class CustomCursorComponent implements OnInit, OnDestroy {
|
|||||||
].join(',');
|
].join(',');
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
|
if (!this.isBrowser) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
this.animate();
|
this.animate();
|
||||||
this.setupClickableDetection();
|
this.setupClickableDetection();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import {
|
|||||||
AfterViewInit,
|
AfterViewInit,
|
||||||
} from '@angular/core';
|
} from '@angular/core';
|
||||||
import { CommonModule } from '@angular/common';
|
import { CommonModule } from '@angular/common';
|
||||||
|
import { injectIsBrowser } from '../../utils/is-browser';
|
||||||
import * as THREE from 'three';
|
import * as THREE from 'three';
|
||||||
|
|
||||||
interface Duck {
|
interface Duck {
|
||||||
@@ -96,6 +97,8 @@ interface Bullet {
|
|||||||
],
|
],
|
||||||
})
|
})
|
||||||
export class DuckShootingGameComponent implements OnInit, AfterViewInit, OnDestroy {
|
export class DuckShootingGameComponent implements OnInit, AfterViewInit, OnDestroy {
|
||||||
|
private readonly isBrowser = injectIsBrowser();
|
||||||
|
|
||||||
@ViewChild('canvas', { static: false }) canvasRef!: ElementRef<HTMLCanvasElement>;
|
@ViewChild('canvas', { static: false }) canvasRef!: ElementRef<HTMLCanvasElement>;
|
||||||
|
|
||||||
private scene!: THREE.Scene;
|
private scene!: THREE.Scene;
|
||||||
@@ -119,6 +122,9 @@ export class DuckShootingGameComponent implements OnInit, AfterViewInit, OnDestr
|
|||||||
ngOnInit(): void {}
|
ngOnInit(): void {}
|
||||||
|
|
||||||
ngAfterViewInit(): void {
|
ngAfterViewInit(): void {
|
||||||
|
if (!this.isBrowser) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
this.initThree();
|
this.initThree();
|
||||||
this.createScene();
|
this.createScene();
|
||||||
this.setupEventListeners();
|
this.setupEventListeners();
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import {
|
|||||||
signal,
|
signal,
|
||||||
} from '@angular/core';
|
} from '@angular/core';
|
||||||
import { CommonModule } from '@angular/common';
|
import { CommonModule } from '@angular/common';
|
||||||
|
import { injectIsBrowser } from '../../utils/is-browser';
|
||||||
import * as THREE from 'three';
|
import * as THREE from 'three';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
@@ -68,6 +69,8 @@ import * as THREE from 'three';
|
|||||||
],
|
],
|
||||||
})
|
})
|
||||||
export class LoadingScreenComponent implements OnInit, AfterViewInit, OnDestroy {
|
export class LoadingScreenComponent implements OnInit, AfterViewInit, OnDestroy {
|
||||||
|
private readonly isBrowser = injectIsBrowser();
|
||||||
|
|
||||||
@ViewChild('canvas', { static: false }) canvasRef!: ElementRef<HTMLCanvasElement>;
|
@ViewChild('canvas', { static: false }) canvasRef!: ElementRef<HTMLCanvasElement>;
|
||||||
@Input() progress = signal(0);
|
@Input() progress = signal(0);
|
||||||
@Input() loadingText = signal('Loading...');
|
@Input() loadingText = signal('Loading...');
|
||||||
@@ -96,6 +99,9 @@ export class LoadingScreenComponent implements OnInit, AfterViewInit, OnDestroy
|
|||||||
}
|
}
|
||||||
|
|
||||||
ngAfterViewInit(): void {
|
ngAfterViewInit(): void {
|
||||||
|
if (!this.isBrowser) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
this.initThree();
|
this.initThree();
|
||||||
this.createParticles();
|
this.createParticles();
|
||||||
this.animate();
|
this.animate();
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import {
|
|||||||
AfterViewInit,
|
AfterViewInit,
|
||||||
} from '@angular/core';
|
} from '@angular/core';
|
||||||
import { CommonModule } from '@angular/common';
|
import { CommonModule } from '@angular/common';
|
||||||
|
import { injectIsBrowser } from '../../utils/is-browser';
|
||||||
import * as THREE from 'three';
|
import * as THREE from 'three';
|
||||||
|
|
||||||
interface Block {
|
interface Block {
|
||||||
@@ -59,6 +60,8 @@ interface Block {
|
|||||||
],
|
],
|
||||||
})
|
})
|
||||||
export class MagicBallGameComponent implements OnInit, AfterViewInit, OnDestroy {
|
export class MagicBallGameComponent implements OnInit, AfterViewInit, OnDestroy {
|
||||||
|
private readonly isBrowser = injectIsBrowser();
|
||||||
|
|
||||||
@ViewChild('canvas', { static: false }) canvasRef!: ElementRef<HTMLCanvasElement>;
|
@ViewChild('canvas', { static: false }) canvasRef!: ElementRef<HTMLCanvasElement>;
|
||||||
|
|
||||||
private scene!: THREE.Scene;
|
private scene!: THREE.Scene;
|
||||||
@@ -78,6 +81,9 @@ export class MagicBallGameComponent implements OnInit, AfterViewInit, OnDestroy
|
|||||||
ngOnInit(): void {}
|
ngOnInit(): void {}
|
||||||
|
|
||||||
ngAfterViewInit(): void {
|
ngAfterViewInit(): void {
|
||||||
|
if (!this.isBrowser) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
this.initThree();
|
this.initThree();
|
||||||
this.createScene();
|
this.createScene();
|
||||||
this.setupEventListeners();
|
this.setupEventListeners();
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import {
|
|||||||
AfterViewInit,
|
AfterViewInit,
|
||||||
} from '@angular/core';
|
} from '@angular/core';
|
||||||
import { CommonModule } from '@angular/common';
|
import { CommonModule } from '@angular/common';
|
||||||
|
import { injectIsBrowser } from '../../utils/is-browser';
|
||||||
import * as THREE from 'three';
|
import * as THREE from 'three';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
@@ -31,6 +32,8 @@ import * as THREE from 'three';
|
|||||||
],
|
],
|
||||||
})
|
})
|
||||||
export class ParticleBackgroundComponent implements OnInit, AfterViewInit, OnDestroy {
|
export class ParticleBackgroundComponent implements OnInit, AfterViewInit, OnDestroy {
|
||||||
|
private readonly isBrowser = injectIsBrowser();
|
||||||
|
|
||||||
@ViewChild('canvas', { static: false }) canvasRef!: ElementRef<HTMLCanvasElement>;
|
@ViewChild('canvas', { static: false }) canvasRef!: ElementRef<HTMLCanvasElement>;
|
||||||
|
|
||||||
private scene!: THREE.Scene;
|
private scene!: THREE.Scene;
|
||||||
@@ -61,6 +64,9 @@ export class ParticleBackgroundComponent implements OnInit, AfterViewInit, OnDes
|
|||||||
ngOnInit(): void {}
|
ngOnInit(): void {}
|
||||||
|
|
||||||
ngAfterViewInit(): void {
|
ngAfterViewInit(): void {
|
||||||
|
if (!this.isBrowser) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
this.initThree();
|
this.initThree();
|
||||||
this.createParticles();
|
this.createParticles();
|
||||||
this.animate();
|
this.animate();
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import {
|
|||||||
AfterViewInit,
|
AfterViewInit,
|
||||||
} from '@angular/core';
|
} from '@angular/core';
|
||||||
import { CommonModule } from '@angular/common';
|
import { CommonModule } from '@angular/common';
|
||||||
|
import { injectIsBrowser } from '../../utils/is-browser';
|
||||||
|
|
||||||
interface Bullet {
|
interface Bullet {
|
||||||
x: number;
|
x: number;
|
||||||
@@ -68,6 +69,8 @@ interface Enemy {
|
|||||||
],
|
],
|
||||||
})
|
})
|
||||||
export class SpaceInvadersComponent implements OnInit, AfterViewInit, OnDestroy {
|
export class SpaceInvadersComponent implements OnInit, AfterViewInit, OnDestroy {
|
||||||
|
private readonly isBrowser = injectIsBrowser();
|
||||||
|
|
||||||
@ViewChild('canvas', { static: false }) canvasRef!: ElementRef<HTMLCanvasElement>;
|
@ViewChild('canvas', { static: false }) canvasRef!: ElementRef<HTMLCanvasElement>;
|
||||||
|
|
||||||
private ctx!: CanvasRenderingContext2D;
|
private ctx!: CanvasRenderingContext2D;
|
||||||
@@ -88,6 +91,9 @@ export class SpaceInvadersComponent implements OnInit, AfterViewInit, OnDestroy
|
|||||||
ngOnInit(): void {}
|
ngOnInit(): void {}
|
||||||
|
|
||||||
ngAfterViewInit(): void {
|
ngAfterViewInit(): void {
|
||||||
|
if (!this.isBrowser) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
const canvas = this.canvasRef.nativeElement;
|
const canvas = this.canvasRef.nativeElement;
|
||||||
this.ctx = canvas.getContext('2d')!;
|
this.ctx = canvas.getContext('2d')!;
|
||||||
this.resizeCanvas();
|
this.resizeCanvas();
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import {
|
|||||||
AfterViewInit,
|
AfterViewInit,
|
||||||
} from '@angular/core';
|
} from '@angular/core';
|
||||||
import { CommonModule } from '@angular/common';
|
import { CommonModule } from '@angular/common';
|
||||||
|
import { injectIsBrowser } from '../../utils/is-browser';
|
||||||
import * as THREE from 'three';
|
import * as THREE from 'three';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
@@ -28,6 +29,8 @@ import * as THREE from 'three';
|
|||||||
],
|
],
|
||||||
})
|
})
|
||||||
export class ThreeGeometricComponent implements OnInit, AfterViewInit, OnDestroy {
|
export class ThreeGeometricComponent implements OnInit, AfterViewInit, OnDestroy {
|
||||||
|
private readonly isBrowser = injectIsBrowser();
|
||||||
|
|
||||||
@ViewChild('canvas', { static: false }) canvasRef!: ElementRef<HTMLCanvasElement>;
|
@ViewChild('canvas', { static: false }) canvasRef!: ElementRef<HTMLCanvasElement>;
|
||||||
@Input() type: 'floating' | 'rings' | 'particles' = 'floating';
|
@Input() type: 'floating' | 'rings' | 'particles' = 'floating';
|
||||||
@Input() color: string = '#3b82f6';
|
@Input() color: string = '#3b82f6';
|
||||||
@@ -41,6 +44,9 @@ export class ThreeGeometricComponent implements OnInit, AfterViewInit, OnDestroy
|
|||||||
ngOnInit(): void {}
|
ngOnInit(): void {}
|
||||||
|
|
||||||
ngAfterViewInit(): void {
|
ngAfterViewInit(): void {
|
||||||
|
if (!this.isBrowser) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
this.initThree();
|
this.initThree();
|
||||||
this.createGeometry();
|
this.createGeometry();
|
||||||
this.animate();
|
this.animate();
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import {
|
|||||||
AfterViewInit,
|
AfterViewInit,
|
||||||
} from '@angular/core';
|
} from '@angular/core';
|
||||||
import { CommonModule } from '@angular/common';
|
import { CommonModule } from '@angular/common';
|
||||||
|
import { injectIsBrowser } from '../../utils/is-browser';
|
||||||
import * as THREE from 'three';
|
import * as THREE from 'three';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
@@ -35,6 +36,8 @@ import * as THREE from 'three';
|
|||||||
],
|
],
|
||||||
})
|
})
|
||||||
export class ThreeTrophyComponent implements OnInit, AfterViewInit, OnDestroy {
|
export class ThreeTrophyComponent implements OnInit, AfterViewInit, OnDestroy {
|
||||||
|
private readonly isBrowser = injectIsBrowser();
|
||||||
|
|
||||||
@ViewChild('canvas', { static: false }) canvasRef!: ElementRef<HTMLCanvasElement>;
|
@ViewChild('canvas', { static: false }) canvasRef!: ElementRef<HTMLCanvasElement>;
|
||||||
@Input() progress: number | null = null;
|
@Input() progress: number | null = null;
|
||||||
@Input() size: number = 200;
|
@Input() size: number = 200;
|
||||||
@@ -50,6 +53,9 @@ export class ThreeTrophyComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||||||
ngOnInit(): void {}
|
ngOnInit(): void {}
|
||||||
|
|
||||||
ngAfterViewInit(): void {
|
ngAfterViewInit(): void {
|
||||||
|
if (!this.isBrowser) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
this.initThree();
|
this.initThree();
|
||||||
this.createTrophy();
|
this.createTrophy();
|
||||||
this.animate();
|
this.animate();
|
||||||
|
|||||||
@@ -0,0 +1,7 @@
|
|||||||
|
import { inject, PLATFORM_ID } from '@angular/core';
|
||||||
|
import { isPlatformBrowser } from '@angular/common';
|
||||||
|
|
||||||
|
/** True when running in a real browser (not during SSR). */
|
||||||
|
export function injectIsBrowser(): boolean {
|
||||||
|
return isPlatformBrowser(inject(PLATFORM_ID));
|
||||||
|
}
|
||||||
Reference in new issue
Block a user