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
This commit is contained in:
@@ -42,6 +42,7 @@
|
|||||||
let isFullscreen = $state(false);
|
let isFullscreen = $state(false);
|
||||||
let zoomed = $state(false);
|
let zoomed = $state(false);
|
||||||
let scale = $state(1);
|
let scale = $state(1);
|
||||||
|
let hiResLoaded = $state(false);
|
||||||
|
|
||||||
let showTop = $state(false);
|
let showTop = $state(false);
|
||||||
let showBottom = $state(false);
|
let showBottom = $state(false);
|
||||||
@@ -60,17 +61,41 @@
|
|||||||
let startL = 0;
|
let startL = 0;
|
||||||
let startT = 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(() => {
|
$effect(() => {
|
||||||
const id = current?.id;
|
const id = current?.id;
|
||||||
fresh = null;
|
fresh = null;
|
||||||
zoomed = false;
|
zoomed = false;
|
||||||
scale = 1;
|
scale = 1;
|
||||||
|
hiResLoaded = false;
|
||||||
if (!id) return;
|
if (!id) return;
|
||||||
api<{ photo: Photo }>(`/photos/${id}`)
|
api<{ photo: Photo }>(`/photos/${id}`)
|
||||||
.then((data) => (fresh = data.photo))
|
.then((data) => (fresh = data.photo))
|
||||||
.catch(() => {});
|
.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(() => {
|
$effect(() => {
|
||||||
const el = scroller;
|
const el = scroller;
|
||||||
if (!el) return;
|
if (!el) return;
|
||||||
@@ -205,10 +230,10 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
function baseWidth(): number {
|
function baseWidth(): number {
|
||||||
return imgEl?.naturalWidth || fresh?.width || current?.width || 0;
|
return fresh?.width || current?.width || imgEl?.naturalWidth || 0;
|
||||||
}
|
}
|
||||||
function baseHeight(): number {
|
function baseHeight(): number {
|
||||||
return imgEl?.naturalHeight || fresh?.height || current?.height || 0;
|
return fresh?.height || current?.height || imgEl?.naturalHeight || 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
function prev() {
|
function prev() {
|
||||||
@@ -219,8 +244,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
function toggleActual() {
|
function toggleActual() {
|
||||||
const atHundred = zoomed && Math.abs(scale - 1) < 0.01;
|
if (zoomed) {
|
||||||
if (atHundred) {
|
|
||||||
zoomed = false;
|
zoomed = false;
|
||||||
scale = 1;
|
scale = 1;
|
||||||
if (scroller) {
|
if (scroller) {
|
||||||
@@ -434,10 +458,7 @@
|
|||||||
{#if current}
|
{#if current}
|
||||||
<img
|
<img
|
||||||
bind:this={imgEl}
|
bind:this={imgEl}
|
||||||
src={fresh?.preview_url ??
|
src={displaySrc}
|
||||||
fresh?.original_url ??
|
|
||||||
current.preview_url ??
|
|
||||||
current.original_url}
|
|
||||||
alt={current.original_filename}
|
alt={current.original_filename}
|
||||||
draggable="false"
|
draggable="false"
|
||||||
style={zoomed ? `width: ${baseWidth() * scale}px; height: auto;` : ''}
|
style={zoomed ? `width: ${baseWidth() * scale}px; height: auto;` : ''}
|
||||||
|
|||||||
Reference in New Issue
Block a user