perf(immersive): improve background quality while maintaining low CPU

- Increase canvas size from 8x8 to 64x64 to reduce visible pixelation
- Add small CSS blur(40px) to smooth the result
- Key: blur(40px) on 64x64 is much cheaper than blur(120px) on full image
- Adjusted brightness slightly darker (0.55x) for better text contrast
This commit is contained in:
vicrodh committed 2026-01-31 11:06:23 -06:00
1 parent f012ba33f4
commit 282fffc6a4
1 file changed
+29 -29
@@ -9,9 +9,9 @@
let isLoading = $state(true); let isLoading = $state(true);
let currentArtwork = $state(''); let currentArtwork = $state('');
// Generate a tiny blurred version using Canvas (GPU-free approach) // Generate a small blurred version using Canvas
// Instead of CSS blur(120px) which kills WebKit performance, // Key insight: blur(30px) on a 64x64 image is MUCH cheaper than blur(120px) on full image
// we resize the image to 8x8 pixels and scale it up - natural blur effect // The small source + small blur = smooth result with minimal CPU
async function generateBlurredBackground(imageUrl: string): Promise<void> { async function generateBlurredBackground(imageUrl: string): Promise<void> {
if (!canvasRef || !imageUrl) return; if (!canvasRef || !imageUrl) return;
@@ -22,35 +22,34 @@
img.crossOrigin = 'anonymous'; img.crossOrigin = 'anonymous';
img.onload = () => { img.onload = () => {
// Draw to tiny size (8x8) - this creates natural blur when scaled // Use 64x64 - large enough to avoid blocky pixels, small enough to be efficient
const tinySize = 8; const size = 64;
canvasRef!.width = tinySize; canvasRef!.width = size;
canvasRef!.height = tinySize; canvasRef!.height = size;
// Draw image scaled down to 8x8 // Draw image scaled down
ctx.drawImage(img, 0, 0, tinySize, tinySize); ctx.drawImage(img, 0, 0, size, size);
// Slightly boost saturation by adjusting colors // Apply color adjustments
const imageData = ctx.getImageData(0, 0, tinySize, tinySize); const imageData = ctx.getImageData(0, 0, size, size);
const data = imageData.data; const data = imageData.data;
for (let i = 0; i < data.length; i += 4) { for (let i = 0; i < data.length; i += 4) {
// Simple saturation boost
const r = data[i]; const r = data[i];
const g = data[i + 1]; const g = data[i + 1];
const b = data[i + 2]; const b = data[i + 2];
const avg = (r + g + b) / 3; const avg = (r + g + b) / 3;
// Saturation factor (1.3x) // Saturation boost (1.3x)
const satFactor = 1.3; const satFactor = 1.3;
data[i] = Math.min(255, avg + (r - avg) * satFactor); let newR = avg + (r - avg) * satFactor;
data[i + 1] = Math.min(255, avg + (g - avg) * satFactor); let newG = avg + (g - avg) * satFactor;
data[i + 2] = Math.min(255, avg + (b - avg) * satFactor); let newB = avg + (b - avg) * satFactor;
// Brightness reduction (0.6x) // Brightness reduction (0.55x) - slightly darker for better text contrast
data[i] = data[i] * 0.6; data[i] = Math.min(255, Math.max(0, newR * 0.55));
data[i + 1] = data[i + 1] * 0.6; data[i + 1] = Math.min(255, Math.max(0, newG * 0.55));
data[i + 2] = data[i + 2] * 0.6; data[i + 2] = Math.min(255, Math.max(0, newB * 0.55));
} }
ctx.putImageData(imageData, 0, 0); ctx.putImageData(imageData, 0, 0);
@@ -96,16 +95,17 @@
.background-canvas { .background-canvas {
position: absolute; position: absolute;
/* Extend beyond viewport for seamless edges */ /* Extend beyond viewport to hide blur edges */
inset: -50px; inset: -80px;
width: calc(100% + 100px); width: calc(100% + 160px);
height: calc(100% + 100px); height: calc(100% + 160px);
/* Scale up tiny canvas - creates natural blur effect */ /* Smooth interpolation when scaling up */
/* Using image-rendering: auto for smooth interpolation */
image-rendering: auto; image-rendering: auto;
/* GPU layer for smooth transitions */ /* Small blur to smooth out the 64x64 canvas - MUCH cheaper than 120px on full image */
transform: scale(1.1) translateZ(0); filter: blur(40px);
will-change: opacity; /* GPU layer for performance */
transform: scale(1.15) translateZ(0);
will-change: opacity, filter;
transition: opacity 500ms ease-out; transition: opacity 500ms ease-out;
} }