From d8da885020935cfb116a99ccad82a0d43010cc64 Mon Sep 17 00:00:00 2001 From: Iu Date: Sat, 10 Oct 2026 19:55:10 +0300 Subject: [PATCH] feat(frontend): viewer zoom loads original and Space resets to fit - in zoom, preload the full-resolution original and swap it in once loaded (preview is shown until then); revert to preview on zoom exit - Space returns to the fitted view from any zoom level; from fit it goes to 100% - derive zoom scale from the photo's true dimensions so 100% is accurate --- frontend/src/lib/components/Viewer.svelte | 37 ++++++++++++++++++----- 1 file changed, 29 insertions(+), 8 deletions(-) diff --git a/frontend/src/lib/components/Viewer.svelte b/frontend/src/lib/components/Viewer.svelte index 8395fca..7e35a10 100644 --- a/frontend/src/lib/components/Viewer.svelte +++ b/frontend/src/lib/components/Viewer.svelte @@ -42,6 +42,7 @@ let isFullscreen = $state(false); let zoomed = $state(false); let scale = $state(1); + let hiResLoaded = $state(false); let showTop = $state(false); let showBottom = $state(false); @@ -60,17 +61,41 @@ let startL = 0; let startT = 0; + const previewSrc = $derived( + fresh?.preview_url ?? fresh?.original_url ?? current?.preview_url ?? current?.original_url ?? '' + ); + const fullSrc = $derived(fresh?.original_url ?? current?.original_url ?? ''); + const displaySrc = $derived(zoomed && hiResLoaded && fullSrc ? fullSrc : previewSrc); + $effect(() => { const id = current?.id; fresh = null; zoomed = false; scale = 1; + hiResLoaded = false; if (!id) return; api<{ photo: Photo }>(`/photos/${id}`) .then((data) => (fresh = data.photo)) .catch(() => {}); }); + // In zoom mode, preload the full-resolution original and swap it in once loaded. + $effect(() => { + if (!zoomed || hiResLoaded) return; + const url = fullSrc; + if (!url) return; + const im = new Image(); + let alive = true; + im.onload = () => { + if (alive) hiResLoaded = true; + }; + im.src = url; + return () => { + alive = false; + im.onload = null; + }; + }); + $effect(() => { const el = scroller; if (!el) return; @@ -205,10 +230,10 @@ } function baseWidth(): number { - return imgEl?.naturalWidth || fresh?.width || current?.width || 0; + return fresh?.width || current?.width || imgEl?.naturalWidth || 0; } function baseHeight(): number { - return imgEl?.naturalHeight || fresh?.height || current?.height || 0; + return fresh?.height || current?.height || imgEl?.naturalHeight || 0; } function prev() { @@ -219,8 +244,7 @@ } function toggleActual() { - const atHundred = zoomed && Math.abs(scale - 1) < 0.01; - if (atHundred) { + if (zoomed) { zoomed = false; scale = 1; if (scroller) { @@ -434,10 +458,7 @@ {#if current}