From 2adcb1374bd648c39428d93cf61ffe59ca41b230 Mon Sep 17 00:00:00 2001 From: Iu Date: Wed, 21 Oct 2026 00:11:17 +0300 Subject: [PATCH] =?UTF-8?q?fix(web):=20=D0=BF=D1=80=D0=B5=D0=B4=D0=BF?= =?UTF-8?q?=D1=80=D0=BE=D1=81=D0=BC=D0=BE=D1=82=D1=80=20=D0=BF=D1=80=D0=B0?= =?UTF-8?q?=D0=B2=D0=BE=D0=BA=20=D1=80=D0=B0=D0=B1=D0=BE=D1=82=D0=B0=D0=B5?= =?UTF-8?q?=D1=82=20=D0=B8=20=D0=B1=D0=B5=D0=B7=20=D0=B7=D1=83=D0=BC=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Клиентский предпросмотр был включён только в зуме, поэтому в обычном виде просмотра фото оставалось без правок. Теперь холст рисует ту часть картинки, что попала во вьюпорт, в любом режиме, а под ним лежит неотредактированная версия фото — так правки не применяются дважды. --- frontend/src/lib/components/Viewer.svelte | 59 ++++++++++++++++------- 1 file changed, 41 insertions(+), 18 deletions(-) diff --git a/frontend/src/lib/components/Viewer.svelte b/frontend/src/lib/components/Viewer.svelte index a742336..977b1d2 100644 --- a/frontend/src/lib/components/Viewer.svelte +++ b/frontend/src/lib/components/Viewer.svelte @@ -77,6 +77,8 @@ '' ); const originalSrc = $derived(fresh?.original_url ?? current?.original_url ?? ''); + // Базовые (неотредактированные) версии: на них браузер рисует предпросмотр. + const basePreviewSrc = $derived(fresh?.preview_url ?? current?.preview_url ?? ''); // Пока сервер не отрендерил текущие правки, видимую область фото рисует // сам браузер: правки видны сразу, без ожидания рендера. @@ -91,12 +93,15 @@ JSON.stringify(edit.params) !== JSON.stringify(savedParams) ); const needClientRender = $derived(hasEdits && (pending || !editUrl)); - const clientPreview = $derived(zoomed && needClientRender); + const clientPreview = $derived(needClientRender); // Пока браузер рисует правки сам, под ним лежит исходная версия фото. + const clientSource = $derived( + zoomed ? originalSrc || basePreviewSrc : basePreviewSrc + ); const displaySrc = $derived( clientPreview - ? originalSrc || previewSrc + ? clientSource || previewSrc : zoomed && hiResLoaded && fullSrc ? fullSrc : previewSrc @@ -176,7 +181,10 @@ let canvas: HTMLCanvasElement | undefined = $state(); let viewportFrame: number | null = null; - /** Рисует видимую область фото с применёнными правками. */ + /** + * Рисует видимую область фото с применёнными правками. Работает и в зуме, + * и в обычном виде: берём ту часть картинки, что попала во вьюпорт. + */ function renderViewport() { const c = canvas; const img = imgEl; @@ -184,8 +192,20 @@ if (!c || !img || !el) return; const natural = img.naturalWidth; if (!natural) return; - const ratio = img.clientWidth / natural; - if (!ratio) return; + const imgRect = img.getBoundingClientRect(); + const viewRect = el.getBoundingClientRect(); + if (!imgRect.width || !viewRect.width) return; + const ratio = imgRect.width / natural; + + // Видимая часть картинки. + const left = Math.max(imgRect.left, viewRect.left); + const top = Math.max(imgRect.top, viewRect.top); + const right = Math.min(imgRect.right, viewRect.right); + const bottom = Math.min(imgRect.bottom, viewRect.bottom); + const visibleW = right - left; + const visibleH = bottom - top; + if (visibleW <= 0 || visibleH <= 0) return; + const width = el.clientWidth; const height = el.clientHeight; if (!width || !height) return; @@ -195,23 +215,25 @@ } c.style.width = `${width}px`; c.style.height = `${height}px`; + const ctx = c.getContext('2d'); if (!ctx) return; ctx.clearRect(0, 0, width, height); - ctx.drawImage( - img, - el.scrollLeft / ratio, - el.scrollTop / ratio, - width / ratio, - height / ratio, - 0, - 0, - width, - height - ); - const data = ctx.getImageData(0, 0, width, height); + + // Исходный кусок в пикселях картинки и его место на холсте. + const sx = (left - imgRect.left) / ratio; + const sy = (top - imgRect.top) / ratio; + const sw = visibleW / ratio; + const sh = visibleH / ratio; + const dx = Math.round(left - viewRect.left); + const dy = Math.round(top - viewRect.top); + const dw = Math.round(visibleW); + const dh = Math.round(visibleH); + + ctx.drawImage(img, sx, sy, sw, sh, dx, dy, dw, dh); + const data = ctx.getImageData(dx, dy, dw, dh); applyLight(data.data, liveParams.light); - ctx.putImageData(data, 0, 0); + ctx.putImageData(data, dx, dy); } function scheduleViewport() { @@ -684,6 +706,7 @@ src={displaySrc} alt={current.original_filename} draggable="false" + onload={scheduleViewport} style={zoomed ? `width: ${baseWidth() * scale}px; height: auto;` : ''} class="m-auto block {zoomed ? 'max-w-none shrink-0 select-none'