картинка масштабируется

main
parent 84ab8fc59f
commit c70fd4e98b

@ -1,18 +1,24 @@
// konva-utils.js // konva-utils.js
export function attachStageResizer(root, stage) {
// attachStageResizer(root, stage, onResize?)
export function attachStageResizer(root, stage, onResize) {
const observer = new ResizeObserver(() => { const observer = new ResizeObserver(() => {
if (!stage) return; if (!stage) return;
stage.width(root.clientWidth); stage.width(root.clientWidth);
stage.height(root.clientHeight); stage.height(root.clientHeight);
stage.batchDraw(); stage.batchDraw();
if (typeof onResize === 'function') {
onResize();
}
}); });
observer.observe(root); observer.observe(root);
return observer; return observer;
} }
// масштабирует картинку по stage // fitImageToStage: вписать целиком (contain)
export function fitImageToStage(konvaImage, stage) { export function fitImageToStage(konvaImage, stage) {
const img = konvaImage.image(); const img = konvaImage.image();
const sw = stage.width(); const sw = stage.width();
@ -25,11 +31,11 @@ export function fitImageToStage(konvaImage, stage) {
konvaImage.height(ih); konvaImage.height(ih);
konvaImage.scale({ x: scale, y: scale }); konvaImage.scale({ x: scale, y: scale });
// центрируем
konvaImage.x((sw - iw * scale) / 2); konvaImage.x((sw - iw * scale) / 2);
konvaImage.y((sh - ih * scale) / 2); konvaImage.y((sh - ih * scale) / 2);
} }
// coverImageToStage: заполнить (cover)
export function coverImageToStage(konvaImage, stage) { export function coverImageToStage(konvaImage, stage) {
const img = konvaImage.image(); const img = konvaImage.image();
const sw = stage.width(); const sw = stage.width();
@ -42,7 +48,106 @@ export function coverImageToStage(konvaImage, stage) {
konvaImage.height(ih); konvaImage.height(ih);
konvaImage.scale({ x: scale, y: scale }); konvaImage.scale({ x: scale, y: scale });
// центрируем и обрезаем по центру
konvaImage.x((sw - iw * scale) / 2); konvaImage.x((sw - iw * scale) / 2);
konvaImage.y((sh - ih * 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;
}

@ -4,4 +4,5 @@ nue2 + konva
## Особенности ## Особенности
- вынес часть кода из компонента в отдельный файл. - вынес часть кода из компонента в отдельный файл.
- 23.12.2025 - картинка загружается, пока бех масштабироваания - 23.12.2025 - картинка загружается, пока без масштабироваания
- 24.12.2025 - теперь с масштабированием (fit/cover)

@ -3,15 +3,17 @@
<script> <script>
import { import {
attachStageResizer, attachStageResizer,
fitImageToStage, addImageWithPreview,
coverImageToStage, // если нужно, можно импортировать fit/cover отдельно
} from "./@shared/konva-utils.js" } from "./@shared/konva-utils.js";
</script> </script>
<konva-editor> <konva-editor>
<script> <script>
this.stage = null; this.stage = null;
this.layer = null;
this._resizeObserver = null; this._resizeObserver = null;
this._cancelImage = null;
mounted() { mounted() {
const width = this.root.clientWidth || 500; const width = this.root.clientWidth || 500;
@ -26,28 +28,47 @@
this.layer = new Konva.Layer(); this.layer = new Konva.Layer();
this.stage.add(this.layer); this.stage.add(this.layer);
this._resizeObserver = attachStageResizer(this.root, this.stage); // передаём колбэк, чтобы при ресайзе пересчитывать cover для всех изображений
this.addImage("1.jpg"); this._resizeObserver = attachStageResizer(this.root, this.stage, () => {
// пересчитать cover для всех изображений на слое (если они уже в cover)
this.layer.find('Image').each(img => {
// можно использовать updateImageToCover или просто coverImageToStage
// импортируй updateImageToCover, если хочешь без анимации
// coverImageToStage(img, this.stage);
// здесь — без анимации, чтобы не запускать много tween'ов при ресайзе
const imgNode = img;
const sw = this.stage.width();
const sh = this.stage.height();
const iw = imgNode.image().naturalWidth;
const ih = imgNode.image().naturalHeight;
const scale = Math.max(sw / iw, sh / ih);
imgNode.width(iw);
imgNode.height(ih);
imgNode.scale({ x: scale, y: scale });
imgNode.x((sw - iw * scale) / 2);
imgNode.y((sh - ih * scale) / 2);
});
this.layer.batchDraw();
});
// добавить картинку: сначала реальный размер, через 500ms — cover
this._cancelImage = addImageWithPreview(this.stage, this.layer, '1.jpg', 500, {
crossOrigin: 'Anonymous',
initialPosition: 'top-left',
coverDuration: 0.25
});
} }
// простой метод добавления картинки через Konva
addImage(url) {
// опционально: добавить crossOrigin, если нужно
Konva.Image.fromURL(url, (konvaImage) => {
coverImageToStage(konvaImage, this.stage); // масштабирование (fit?)
konvaImage.setAttrs({ x: 0, y: 0, draggable: true, });
this.layer.add(konvaImage);
this.layer.draw();
}, { crossOrigin: 'Anonymous' });
};
unmounted() { unmounted() {
// отменяем таймер/анимацию, если есть
this._cancelImage?.();
this._resizeObserver?.disconnect(); this._resizeObserver?.disconnect();
this._resizeObserver = null; this._resizeObserver = null;
this.stage?.destroy(); this.stage?.destroy();
this.stage = null; this.stage = null;
this.layer = null;
} }
</script> </script>
</konva-editor> </konva-editor>

Loading…
Cancel
Save