Files
qbz/src/lib/components/immersive/ImmersiveBackground.svelte
T
vicrodh 282fffc6a4 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
2026-01-31 11:06:23 -06:00

124 lines
3.3 KiB
Svelte

<script lang="ts">
interface Props {
artwork: string;
}
let { artwork }: Props = $props();
let canvasRef: HTMLCanvasElement | undefined = $state();
let isLoading = $state(true);
let currentArtwork = $state('');
// Generate a small blurred version using Canvas
// Key insight: blur(30px) on a 64x64 image is MUCH cheaper than blur(120px) on full image
// The small source + small blur = smooth result with minimal CPU
async function generateBlurredBackground(imageUrl: string): Promise<void> {
if (!canvasRef || !imageUrl) return;
const ctx = canvasRef.getContext('2d');
if (!ctx) return;
const img = new Image();
img.crossOrigin = 'anonymous';
img.onload = () => {
// Use 64x64 - large enough to avoid blocky pixels, small enough to be efficient
const size = 64;
canvasRef!.width = size;
canvasRef!.height = size;
// Draw image scaled down
ctx.drawImage(img, 0, 0, size, size);
// Apply color adjustments
const imageData = ctx.getImageData(0, 0, size, size);
const data = imageData.data;
for (let i = 0; i < data.length; i += 4) {
const r = data[i];
const g = data[i + 1];
const b = data[i + 2];
const avg = (r + g + b) / 3;
// Saturation boost (1.3x)
const satFactor = 1.3;
let newR = avg + (r - avg) * satFactor;
let newG = avg + (g - avg) * satFactor;
let newB = avg + (b - avg) * satFactor;
// Brightness reduction (0.55x) - slightly darker for better text contrast
data[i] = Math.min(255, Math.max(0, newR * 0.55));
data[i + 1] = Math.min(255, Math.max(0, newG * 0.55));
data[i + 2] = Math.min(255, Math.max(0, newB * 0.55));
}
ctx.putImageData(imageData, 0, 0);
isLoading = false;
};
img.onerror = () => {
isLoading = false;
};
img.src = imageUrl;
}
// Track artwork changes
$effect(() => {
if (!artwork || artwork === currentArtwork) return;
currentArtwork = artwork;
isLoading = true;
generateBlurredBackground(artwork);
});
</script>
<div class="immersive-background" class:loading={isLoading}>
<!-- Canvas-generated tiny image, scaled up via CSS -->
<canvas
bind:this={canvasRef}
class="background-canvas"
aria-hidden="true"
></canvas>
<!-- Dark overlay for better contrast -->
<div class="dark-overlay"></div>
</div>
<style>
.immersive-background {
position: absolute;
inset: 0;
overflow: hidden;
z-index: 0;
background-color: #0a0a0b;
}
.background-canvas {
position: absolute;
/* Extend beyond viewport to hide blur edges */
inset: -80px;
width: calc(100% + 160px);
height: calc(100% + 160px);
/* Smooth interpolation when scaling up */
image-rendering: auto;
/* Small blur to smooth out the 64x64 canvas - MUCH cheaper than 120px on full image */
filter: blur(40px);
/* GPU layer for performance */
transform: scale(1.15) translateZ(0);
will-change: opacity, filter;
transition: opacity 500ms ease-out;
}
.loading .background-canvas {
opacity: 0;
}
/* Subtle dark overlay */
.dark-overlay {
position: absolute;
inset: 0;
background: rgba(0, 0, 0, 0.15);
pointer-events: none;
}
</style>