diff --git a/@shared/konva-utils.js b/@shared/konva-utils.js index a9fabf0..8e2ce8f 100644 --- a/@shared/konva-utils.js +++ b/@shared/konva-utils.js @@ -1,48 +1,153 @@ -// konva-utils.js -export function attachStageResizer(root, stage) { - const observer = new ResizeObserver(() => { - if (!stage) return; - - stage.width(root.clientWidth); - stage.height(root.clientHeight); - stage.batchDraw(); - }); - - observer.observe(root); - return observer; -} - -// масштабирует картинку по stage -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); -} - -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); -} +// 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; +} diff --git a/README.md b/README.md index 3e49ff7..31ace03 100644 --- a/README.md +++ b/README.md @@ -4,4 +4,5 @@ nue2 + konva ## Особенности - вынес часть кода из компонента в отдельный файл. -- 23.12.2025 - картинка загружается, пока бех масштабироваания +- 23.12.2025 - картинка загружается, пока без масштабироваания +- 24.12.2025 - теперь с масштабированием (fit/cover) diff --git a/konva-editor.html b/konva-editor.html index bd21599..9e57906 100644 --- a/konva-editor.html +++ b/konva-editor.html @@ -1,17 +1,19 @@