perf: reduce GC pressure in render loops
- Pre-compute star RGB components to avoid 3 string.replace() calls per star per frame (~40K string allocs/sec eliminated) - Cache floor gradient object; rebuild only on canvas resize - Batch CAVA bar DOM updates through requestAnimationFrame so SSE events never trigger mid-frame style mutations Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
e7a95c8c86
commit
1957fda57e
1 changed files with 36 additions and 24 deletions
|
|
@ -794,16 +794,19 @@ const ctx = canvas.getContext('2d');
|
|||
const STAR_COUNT = 220;
|
||||
const stars = [];
|
||||
|
||||
const STAR_COLORS = [[255,221,170],[204,216,255],[238,238,255]];
|
||||
|
||||
function initStars() {
|
||||
stars.length = 0;
|
||||
for (let i = 0; i < STAR_COUNT; i++) {
|
||||
const c = STAR_COLORS[Math.random() < 0.15 ? 0 : Math.random() < 0.5 ? 1 : 2];
|
||||
stars.push({
|
||||
x: Math.random() * canvas.width,
|
||||
y: Math.random() * canvas.height * 0.62,
|
||||
r: Math.random() * 1.6 + 0.2,
|
||||
phase: Math.random() * Math.PI * 2,
|
||||
freq: 0.4 + Math.random() * 1.2,
|
||||
color: Math.random() < 0.15 ? '#ffddaa' : (Math.random() < 0.5 ? '#ccd8ff' : '#eeeeff'),
|
||||
cr: c[0], cg: c[1], cb: c[2],
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -818,13 +821,10 @@ function drawStars(ts) {
|
|||
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
||||
const t = ts * 0.001;
|
||||
stars.forEach(s => {
|
||||
const alpha = 0.25 + 0.75 * (0.5 + 0.5 * Math.sin(t * s.freq + s.phase));
|
||||
const alpha = (0.25 + 0.75 * (0.5 + 0.5 * Math.sin(t * s.freq + s.phase))).toFixed(2);
|
||||
ctx.beginPath();
|
||||
ctx.arc(s.x, s.y, s.r, 0, Math.PI * 2);
|
||||
ctx.fillStyle = s.color.replace(')', `, ${alpha.toFixed(2)})`)
|
||||
.replace('rgb', 'rgba').replace('#ccd8ff', `rgba(204,216,255,${alpha.toFixed(2)})`)
|
||||
.replace('#eeeeff', `rgba(238,238,255,${alpha.toFixed(2)})`)
|
||||
.replace('#ffddaa', `rgba(255,221,170,${alpha.toFixed(2)})`);
|
||||
ctx.fillStyle = `rgba(${s.cr},${s.cg},${s.cb},${alpha})`;
|
||||
ctx.fill();
|
||||
});
|
||||
requestAnimationFrame(drawStars);
|
||||
|
|
@ -845,6 +845,8 @@ const V_LINES = 24;
|
|||
const H_LINES = 24;
|
||||
let floorScroll = 0;
|
||||
let lastFloorTs = 0;
|
||||
let floorFadeGrad = null;
|
||||
let floorFadeGradH = 0;
|
||||
|
||||
function resizeFloor() {
|
||||
floorCanvas.width = window.innerWidth;
|
||||
|
|
@ -891,11 +893,14 @@ function drawFloor(ts) {
|
|||
floorCtx.stroke();
|
||||
}
|
||||
|
||||
// Fade near horizon to hide sub-pixel lines
|
||||
const fade = floorCtx.createLinearGradient(0, 0, 0, H * 0.3);
|
||||
fade.addColorStop(0, '#000814');
|
||||
fade.addColorStop(1, 'rgba(0,8,20,0)');
|
||||
floorCtx.fillStyle = fade;
|
||||
// Fade near horizon to hide sub-pixel lines (gradient cached, only rebuilt on resize)
|
||||
if (!floorFadeGrad || floorFadeGradH !== H) {
|
||||
floorFadeGradH = H;
|
||||
floorFadeGrad = floorCtx.createLinearGradient(0, 0, 0, H * 0.3);
|
||||
floorFadeGrad.addColorStop(0, '#000814');
|
||||
floorFadeGrad.addColorStop(1, 'rgba(0,8,20,0)');
|
||||
}
|
||||
floorCtx.fillStyle = floorFadeGrad;
|
||||
floorCtx.fillRect(0, 0, W, H * 0.3);
|
||||
|
||||
requestAnimationFrame(drawFloor);
|
||||
|
|
@ -983,21 +988,28 @@ function detectBeat(vals) {
|
|||
bassEma = bassEma * 0.96 + bass * 0.04;
|
||||
}
|
||||
|
||||
// Called with semicolon-separated CAVA values (0–100 per bar)
|
||||
function updateBars(raw) {
|
||||
if (!isPlaying) return;
|
||||
const vals = raw.split(';').map(v => Math.min(100, parseInt(v, 10) || 0));
|
||||
detectBeat(vals);
|
||||
// Called with semicolon-separated CAVA values (0–100 per bar) — SSE may fire >60fps,
|
||||
// so we buffer and flush once per animation frame to avoid mid-frame DOM thrashing.
|
||||
let pendingCava = null;
|
||||
|
||||
// VU meter (low bars)
|
||||
function updateBars(raw) {
|
||||
pendingCava = raw;
|
||||
}
|
||||
|
||||
function flushBars() {
|
||||
if (pendingCava && isPlaying) {
|
||||
const vals = pendingCava.split(';').map(v => Math.min(100, parseInt(v, 10) || 0));
|
||||
detectBeat(vals);
|
||||
vuBars.forEach((bar, i) => {
|
||||
const v = vals[i] || 0;
|
||||
bar.style.height = Math.max(3, (v / 100) * MAX_BAR_H) + 'px';
|
||||
bar.style.opacity = 0.3 + 0.7 * (v / 100);
|
||||
});
|
||||
|
||||
|
||||
pendingCava = null;
|
||||
}
|
||||
requestAnimationFrame(flushBars);
|
||||
}
|
||||
requestAnimationFrame(flushBars);
|
||||
|
||||
/* ============================================================
|
||||
MUSIC-LIGHT THEME (album color)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue