perf: reduce splash animation startup pressure

Throttle enhanced splash rendering to 30fps and disable the canvas scene during slow or failed startup steps so the renderer has less work while bootstrapping.
This commit is contained in:
777genius 2026-05-24 00:23:11 +03:00
parent e9cebe64ff
commit 192f2ea497
4 changed files with 49 additions and 12 deletions

View file

@ -16,6 +16,7 @@ declare global {
interface Window {
__claudeTeamsSplashEnhancedStartedAt?: number;
__claudeTeamsSplashScene?: SplashSceneHandle;
__claudeTeamsSplashEnhancedDisabled?: boolean;
__claudeTeamsSplashStartedAt?: number;
}
}
@ -38,7 +39,11 @@ export const App = (): React.JSX.Element => {
const splash = document.getElementById('splash');
if (splash) {
const reducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
const scene = window.__claudeTeamsSplashScene ?? startSplashScene(splash, { reducedMotion });
const scene: SplashSceneHandle =
window.__claudeTeamsSplashScene ??
(window.__claudeTeamsSplashEnhancedDisabled
? { stop: () => undefined, ready: Promise.resolve() }
: startSplashScene(splash, { reducedMotion }));
const startedAt = window.__claudeTeamsSplashStartedAt ?? performance.now();
const enhancedStartedAt = window.__claudeTeamsSplashEnhancedStartedAt ?? performance.now();
const elapsed = performance.now() - startedAt;
@ -62,6 +67,7 @@ export const App = (): React.JSX.Element => {
scene.stop();
window.__claudeTeamsSplashScene = undefined;
window.__claudeTeamsSplashEnhancedStartedAt = undefined;
window.__claudeTeamsSplashEnhancedDisabled = undefined;
splash.remove();
}, fadeDuration);
};

View file

@ -71,6 +71,7 @@ const TEAM_MEMBER_COUNTS = [4, 3, 5] as const;
const TEAM_MEMBER_OFFSETS = [0, 4, 7] as const;
const TEAM_LABELS = ['Marketing', 'Researchers', 'Coding'] as const;
const MAX_DPR = 2;
const SPLASH_SCENE_FRAME_INTERVAL_MS = 1000 / 30;
const avatarCache = new Map<string, HTMLImageElement>();
const avatarLoading = new Map<string, Promise<HTMLImageElement | null>>();
@ -112,6 +113,7 @@ export function startSplashScene(
particles: [] as DepthParticle[],
running: true,
frameId: 0,
lastRenderedAt: 0,
startedAt: performance.now(),
};
@ -139,9 +141,16 @@ export function startSplashScene(
const render = (now: number): void => {
if (!state.running) return;
resize();
const time = (now - state.startedAt) / 1000;
drawScene(ctx, state.width, state.height, time, state.particles, reducedMotion);
if (
reducedMotion ||
state.lastRenderedAt === 0 ||
now - state.lastRenderedAt >= SPLASH_SCENE_FRAME_INTERVAL_MS
) {
resize();
const time = (now - state.startedAt) / 1000;
drawScene(ctx, state.width, state.height, time, state.particles, reducedMotion);
state.lastRenderedAt = now;
}
if (!reducedMotion) {
state.frameId = window.requestAnimationFrame(render);

View file

@ -561,6 +561,7 @@
var TEAM_MEMBER_OFFSETS = [0, 4, 7];
var TEAM_LABELS = ['Marketing', 'Researchers', 'Coding'];
var MAX_DPR = 2;
var SPLASH_SCENE_FRAME_INTERVAL_MS = 1000 / 30;
var FALLBACK_AVATAR_URLS = [
'./assets/participant-avatars/01.png',
'./assets/participant-avatars/02.png',
@ -637,6 +638,7 @@
particles: [],
running: true,
frameId: 0,
lastRenderedAt: 0,
startedAt: performance.now(),
};
@ -661,14 +663,21 @@
function render(now) {
if (!state.running) return;
resize();
drawScene(
ctx,
state.width,
state.height,
reducedMotion ? 1.2 : (now - state.startedAt) / 1000,
state.particles
);
if (
reducedMotion ||
state.lastRenderedAt === 0 ||
now - state.lastRenderedAt >= SPLASH_SCENE_FRAME_INTERVAL_MS
) {
resize();
drawScene(
ctx,
state.width,
state.height,
reducedMotion ? 1.2 : (now - state.startedAt) / 1000,
state.particles
);
state.lastRenderedAt = now;
}
if (!reducedMotion) state.frameId = window.requestAnimationFrame(render);
}

View file

@ -9,12 +9,15 @@ import { App } from './App';
import { initSentryRenderer } from './sentry';
import { initializeNotificationListeners } from './store';
import type { SplashSceneHandle } from './components/splash/splashScene';
import type { AppStartupStatus, AppStartupStep } from '@shared/types/api';
declare global {
interface Window {
__claudeTeamsUiDidInit?: boolean;
__claudeTeamsSplashStaticTimer?: number;
__claudeTeamsSplashScene?: SplashSceneHandle;
__claudeTeamsSplashEnhancedDisabled?: boolean;
}
}
@ -170,6 +173,11 @@ function stopStartupTicker(): void {
startupTicker = undefined;
}
function stopEnhancedSplashScene(): void {
window.__claudeTeamsSplashEnhancedDisabled = true;
window.__claudeTeamsSplashScene?.stop();
}
function mountApp(): void {
if (root) return;
@ -204,6 +212,11 @@ async function bootstrapRenderer(): Promise<void> {
return;
}
updateStartupSplash(nextStatus);
const currentStep = getCurrentStartupStep(nextStatus);
const stepElapsedMs = getStepElapsedMs(currentStep, nextStatus);
if (!nextStatus.ready && (nextStatus.error || stepElapsedMs >= VERY_SLOW_STEP_MS)) {
stopEnhancedSplashScene();
}
if (nextStatus.ready) {
finished = true;
cleanup();