fix(web): предпросмотр правок работает и без зума

Клиентский предпросмотр был включён только в зуме, поэтому в обычном виде
просмотра фото оставалось без правок. Теперь холст рисует ту часть картинки,
что попала во вьюпорт, в любом режиме, а под ним лежит неотредактированная
версия фото — так правки не применяются дважды.
This commit is contained in:
iu
2026-10-21 00:11:17 +03:00
parent 520886a11d
commit 2adcb1374b
+41 -18
View File
@@ -77,6 +77,8 @@
'' ''
); );
const originalSrc = $derived(fresh?.original_url ?? current?.original_url ?? ''); 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) JSON.stringify(edit.params) !== JSON.stringify(savedParams)
); );
const needClientRender = $derived(hasEdits && (pending || !editUrl)); 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( const displaySrc = $derived(
clientPreview clientPreview
? originalSrc || previewSrc ? clientSource || previewSrc
: zoomed && hiResLoaded && fullSrc : zoomed && hiResLoaded && fullSrc
? fullSrc ? fullSrc
: previewSrc : previewSrc
@@ -176,7 +181,10 @@
let canvas: HTMLCanvasElement | undefined = $state(); let canvas: HTMLCanvasElement | undefined = $state();
let viewportFrame: number | null = null; let viewportFrame: number | null = null;
/** Рисует видимую область фото с применёнными правками. */ /**
* Рисует видимую область фото с применёнными правками. Работает и в зуме,
* и в обычном виде: берём ту часть картинки, что попала во вьюпорт.
*/
function renderViewport() { function renderViewport() {
const c = canvas; const c = canvas;
const img = imgEl; const img = imgEl;
@@ -184,8 +192,20 @@
if (!c || !img || !el) return; if (!c || !img || !el) return;
const natural = img.naturalWidth; const natural = img.naturalWidth;
if (!natural) return; if (!natural) return;
const ratio = img.clientWidth / natural; const imgRect = img.getBoundingClientRect();
if (!ratio) return; 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 width = el.clientWidth;
const height = el.clientHeight; const height = el.clientHeight;
if (!width || !height) return; if (!width || !height) return;
@@ -195,23 +215,25 @@
} }
c.style.width = `${width}px`; c.style.width = `${width}px`;
c.style.height = `${height}px`; c.style.height = `${height}px`;
const ctx = c.getContext('2d'); const ctx = c.getContext('2d');
if (!ctx) return; if (!ctx) return;
ctx.clearRect(0, 0, width, height); ctx.clearRect(0, 0, width, height);
ctx.drawImage(
img, // Исходный кусок в пикселях картинки и его место на холсте.
el.scrollLeft / ratio, const sx = (left - imgRect.left) / ratio;
el.scrollTop / ratio, const sy = (top - imgRect.top) / ratio;
width / ratio, const sw = visibleW / ratio;
height / ratio, const sh = visibleH / ratio;
0, const dx = Math.round(left - viewRect.left);
0, const dy = Math.round(top - viewRect.top);
width, const dw = Math.round(visibleW);
height const dh = Math.round(visibleH);
);
const data = ctx.getImageData(0, 0, width, height); ctx.drawImage(img, sx, sy, sw, sh, dx, dy, dw, dh);
const data = ctx.getImageData(dx, dy, dw, dh);
applyLight(data.data, liveParams.light); applyLight(data.data, liveParams.light);
ctx.putImageData(data, 0, 0); ctx.putImageData(data, dx, dy);
} }
function scheduleViewport() { function scheduleViewport() {
@@ -684,6 +706,7 @@
src={displaySrc} src={displaySrc}
alt={current.original_filename} alt={current.original_filename}
draggable="false" draggable="false"
onload={scheduleViewport}
style={zoomed ? `width: ${baseWidth() * scale}px; height: auto;` : ''} style={zoomed ? `width: ${baseWidth() * scale}px; height: auto;` : ''}
class="m-auto block {zoomed class="m-auto block {zoomed
? 'max-w-none shrink-0 select-none' ? 'max-w-none shrink-0 select-none'