fix(web): предпросмотр правок работает и без зума
Клиентский предпросмотр был включён только в зуме, поэтому в обычном виде просмотра фото оставалось без правок. Теперь холст рисует ту часть картинки, что попала во вьюпорт, в любом режиме, а под ним лежит неотредактированная версия фото — так правки не применяются дважды.
This commit is contained in:
@@ -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'
|
||||||
|
|||||||
Reference in New Issue
Block a user