You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

154 lines
4.8 KiB
JavaScript

// konva-utils.js
// attachStageResizer(root, stage, onResize?)
export function attachStageResizer(root, stage, onResize) {
const observer = new ResizeObserver(() => {
if (!stage) return;
stage.width(root.clientWidth);
stage.height(root.clientHeight);
stage.batchDraw();
if (typeof onResize === 'function') {
onResize();
}
});
observer.observe(root);
return observer;
}
// fitImageToStage: вписать целиком (contain)
export function fitImageToStage(konvaImage, stage) {
const img = konvaImage.image();
const sw = stage.width();
const sh = stage.height();
const iw = img.naturalWidth;
const ih = img.naturalHeight;
const scale = Math.min(sw / iw, sh / ih);
konvaImage.width(iw);
konvaImage.height(ih);
konvaImage.scale({ x: scale, y: scale });
konvaImage.x((sw - iw * scale) / 2);
konvaImage.y((sh - ih * scale) / 2);
}
// coverImageToStage: заполнить (cover)
export function coverImageToStage(konvaImage, stage) {
const img = konvaImage.image();
const sw = stage.width();
const sh = stage.height();
const iw = img.naturalWidth;
const ih = img.naturalHeight;
const scale = Math.max(sw / iw, sh / ih);
konvaImage.width(iw);
konvaImage.height(ih);
konvaImage.scale({ x: scale, y: scale });
konvaImage.x((sw - iw * scale) / 2);
konvaImage.y((sh - ih * scale) / 2);
}
// возвращает { scale, x, y } для cover
export function calcCoverTransform(konvaImage, stage) {
const img = konvaImage.image();
const sw = stage.width();
const sh = stage.height();
const iw = img.naturalWidth;
const ih = img.naturalHeight;
const scale = Math.max(sw / iw, sh / ih);
const targetWidth = iw * scale;
const targetHeight = ih * scale;
const x = (sw - targetWidth) / 2;
const y = (sh - targetHeight) / 2;
return { scale, x, y };
}
// Обновить существующее изображение до cover (без анимации)
export function updateImageToCover(konvaImage, stage) {
const { scale, x, y } = calcCoverTransform(konvaImage, stage);
konvaImage.width(konvaImage.image().naturalWidth);
konvaImage.height(konvaImage.image().naturalHeight);
konvaImage.scale({ x: scale, y: scale });
konvaImage.x(x);
konvaImage.y(y);
}
// Добавить картинку: сначала показать в реальном размере, через delayMs анимированно перейти в cover.
// Возвращает функцию cancel(), которую нужно вызывать при unmounted.
export function addImageWithPreview(stage, layer, url, delayMs = 1200, opts = {}) {
// opts: { crossOrigin, initialPosition: 'top-left'|'center', coverDuration }
const crossOrigin = opts.crossOrigin ?? 'Anonymous';
const initialPosition = opts.initialPosition ?? 'top-left';
const coverDuration = opts.coverDuration ?? 0.45;
let timeoutId = null;
let activeTween = null;
let konvaImage = null;
// helper для очистки
function cancel() {
if (timeoutId) {
clearTimeout(timeoutId);
timeoutId = null;
}
if (activeTween) {
// finish or destroy tween
try { activeTween.finish(); } catch (e) { /* ignore */ }
activeTween = null;
}
// не удаляем сам konvaImage — оставляем на слое, но можно удалить при желании
}
Konva.Image.fromURL(url, (imgNode) => {
konvaImage = imgNode;
const img = konvaImage.image();
const iw = img.naturalWidth;
const ih = img.naturalHeight;
// показать в реальном размере (scale = 1)
konvaImage.width(iw);
konvaImage.height(ih);
konvaImage.scale({ x: 1, y: 1 });
if (initialPosition === 'center') {
// центрируем реальный размер по сцене
const sw = stage.width();
const sh = stage.height();
konvaImage.x((sw - iw) / 2);
konvaImage.y((sh - ih) / 2);
} else {
konvaImage.x(0);
konvaImage.y(0);
}
konvaImage.draggable(true);
layer.add(konvaImage);
layer.draw();
// через delayMs — плавно переводим в cover
if (delayMs > 0) {
timeoutId = setTimeout(() => {
timeoutId = null;
const { scale, x, y } = calcCoverTransform(konvaImage, stage);
// анимируем: изменяем scaleX/scaleY и x/y
activeTween = konvaImage.to({
duration: coverDuration,
scaleX: scale,
scaleY: scale,
x,
y,
easing: Konva.Easings.EaseInOut,
onFinish: () => { activeTween = null; }
});
}, delayMs);
}
}, { crossOrigin });
return cancel;
}