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 STAR_COUNT = 220;
|
||||||
const stars = [];
|
const stars = [];
|
||||||
|
|
||||||
|
const STAR_COLORS = [[255,221,170],[204,216,255],[238,238,255]];
|
||||||
|
|
||||||
function initStars() {
|
function initStars() {
|
||||||
stars.length = 0;
|
stars.length = 0;
|
||||||
for (let i = 0; i < STAR_COUNT; i++) {
|
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({
|
stars.push({
|
||||||
x: Math.random() * canvas.width,
|
x: Math.random() * canvas.width,
|
||||||
y: Math.random() * canvas.height * 0.62,
|
y: Math.random() * canvas.height * 0.62,
|
||||||
r: Math.random() * 1.6 + 0.2,
|
r: Math.random() * 1.6 + 0.2,
|
||||||
phase: Math.random() * Math.PI * 2,
|
phase: Math.random() * Math.PI * 2,
|
||||||
freq: 0.4 + Math.random() * 1.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);
|
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
||||||
const t = ts * 0.001;
|
const t = ts * 0.001;
|
||||||
stars.forEach(s => {
|
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.beginPath();
|
||||||
ctx.arc(s.x, s.y, s.r, 0, Math.PI * 2);
|
ctx.arc(s.x, s.y, s.r, 0, Math.PI * 2);
|
||||||
ctx.fillStyle = s.color.replace(')', `, ${alpha.toFixed(2)})`)
|
ctx.fillStyle = `rgba(${s.cr},${s.cg},${s.cb},${alpha})`;
|
||||||
.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.fill();
|
ctx.fill();
|
||||||
});
|
});
|
||||||
requestAnimationFrame(drawStars);
|
requestAnimationFrame(drawStars);
|
||||||
|
|
@ -845,6 +845,8 @@ const V_LINES = 24;
|
||||||
const H_LINES = 24;
|
const H_LINES = 24;
|
||||||
let floorScroll = 0;
|
let floorScroll = 0;
|
||||||
let lastFloorTs = 0;
|
let lastFloorTs = 0;
|
||||||
|
let floorFadeGrad = null;
|
||||||
|
let floorFadeGradH = 0;
|
||||||
|
|
||||||
function resizeFloor() {
|
function resizeFloor() {
|
||||||
floorCanvas.width = window.innerWidth;
|
floorCanvas.width = window.innerWidth;
|
||||||
|
|
@ -891,11 +893,14 @@ function drawFloor(ts) {
|
||||||
floorCtx.stroke();
|
floorCtx.stroke();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fade near horizon to hide sub-pixel lines
|
// Fade near horizon to hide sub-pixel lines (gradient cached, only rebuilt on resize)
|
||||||
const fade = floorCtx.createLinearGradient(0, 0, 0, H * 0.3);
|
if (!floorFadeGrad || floorFadeGradH !== H) {
|
||||||
fade.addColorStop(0, '#000814');
|
floorFadeGradH = H;
|
||||||
fade.addColorStop(1, 'rgba(0,8,20,0)');
|
floorFadeGrad = floorCtx.createLinearGradient(0, 0, 0, H * 0.3);
|
||||||
floorCtx.fillStyle = fade;
|
floorFadeGrad.addColorStop(0, '#000814');
|
||||||
|
floorFadeGrad.addColorStop(1, 'rgba(0,8,20,0)');
|
||||||
|
}
|
||||||
|
floorCtx.fillStyle = floorFadeGrad;
|
||||||
floorCtx.fillRect(0, 0, W, H * 0.3);
|
floorCtx.fillRect(0, 0, W, H * 0.3);
|
||||||
|
|
||||||
requestAnimationFrame(drawFloor);
|
requestAnimationFrame(drawFloor);
|
||||||
|
|
@ -983,22 +988,29 @@ function detectBeat(vals) {
|
||||||
bassEma = bassEma * 0.96 + bass * 0.04;
|
bassEma = bassEma * 0.96 + bass * 0.04;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Called with semicolon-separated CAVA values (0–100 per bar)
|
// 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;
|
||||||
|
|
||||||
function updateBars(raw) {
|
function updateBars(raw) {
|
||||||
if (!isPlaying) return;
|
pendingCava = raw;
|
||||||
const vals = raw.split(';').map(v => Math.min(100, parseInt(v, 10) || 0));
|
|
||||||
detectBeat(vals);
|
|
||||||
|
|
||||||
// VU meter (low bars)
|
|
||||||
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);
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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)
|
MUSIC-LIGHT THEME (album color)
|
||||||
============================================================ */
|
============================================================ */
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue