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:
parent
e9cebe64ff
commit
192f2ea497
4 changed files with 49 additions and 12 deletions
|
|
@ -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);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
Loading…
Reference in a new issue