React-konva移动缩放和可拖动图像



我使用Konva.js来构建一个画布应用程序。这个应用程序允许用户在任何时候拖动图像并缩放。这在桌面上完美地工作。然而,我喜欢这个应用程序对移动设备的响应。这里的问题是画布缩放和图像拖动不能同步工作。由于'onTouchMove'事件在图像拖动时工作,因此此应用程序无法按预期工作。

import React, { useEffect, useState, useRef } from 'react';
import {
Stage, Layer, Image,
} from 'react-konva';
import Rectangle from './rectangle.component';
const GenericCanvas = ({
canvasWidth,
canvasHeight,
imageUrl,
imgWidth,
imgHeight,
rects,
zoom,
imageMove,
alpha,
borderDash,
rectDraggable,
fillStatus,
onClickRect,
reset,
setReset,
imageType,
displayedFigure,
displayedTable,
clickedRect
}) => {
const [image, setImage] = useState(null);
const [stageScale, setStageScale] = useState(1);
const [stageX, setStageX] = useState(0);
const [stageY, setStageY] = useState(0);
const [lastX, setLastX] = useState(0);
const [lastY, setLastY] = useState(0);
const [hasImageLoaded, setHasImageLoaded] = useState(false);
const handleImageLastPosition = (e) => {
setLastX(e.target.attrs.x);
setLastY(e.target.attrs.y);
};
const handleWheel = (e) => {
e.evt.preventDefault();
const scaleBy = 1.2;
const stage = e.target.getStage();
const oldScale = stage.scaleX();
const mousePointTo = {
x: stage.getPointerPosition().x / oldScale - stage.x() / oldScale,
y: stage.getPointerPosition().y / oldScale - stage.y() / oldScale,
};
const newScale = e.evt.deltaY > 0
? (oldScale > 3 ? oldScale : (oldScale * scaleBy)) : oldScale < 1
? oldScale : (oldScale / scaleBy);
setStageScale(newScale);
setStageX(-(mousePointTo.x - stage.getPointerPosition().x / newScale) * newScale);
setStageY(-(mousePointTo.y - stage.getPointerPosition().y / newScale) * newScale);
};
function getDistance(p1, p2) {
return Math.sqrt(Math.pow(p2.x - p1.x, 2) + Math.pow(p2.y - p1.y, 2));
}
function getCenter(p1, p2) {
return {
x: (p1.x + p2.x) / 2,
y: (p1.y + p2.y) / 2,
};
}
var lastCenter = null;
var lastDist = 0;
const handleMultiTouch = (e) => {
e.evt.preventDefault();
var touch1 = e.evt.touches[0];
var touch2 = e.evt.touches[1];
const stage = e.target.getStage();
if (touch1 && touch2) {
if (stage.isDragging()) {
stage.stopDrag();
}
imageMove= false;
var p1 = {
x: touch1.clientX,
y: touch1.clientY,
};
var p2 = {
x: touch2.clientX,
y: touch2.clientY,
};
if (!lastCenter) {
lastCenter = getCenter(p1, p2);
return;
}
var newCenter = getCenter(p1, p2);
var dist = getDistance(p1, p2);
if (!lastDist) {
lastDist = dist;
}
// local coordinates of center point
var pointTo = {
x: (newCenter.x - stage.x()) / stage.scaleX(),
y: (newCenter.y - stage.y()) / stage.scaleX(),
};
var scale = stage.scaleX() * (dist / lastDist);
stage.scaleX(scale);
stage.scaleY(scale);
// calculate new position of the stage
var dx = newCenter.x - lastCenter.x;
var dy = newCenter.y - lastCenter.y;
var newPos = {
x: newCenter.x - pointTo.x * scale + dx,
y: newCenter.y - pointTo.y * scale + dy,
};
stage.position(newPos);
stage.batchDraw();
lastDist = dist;
lastCenter = newCenter;
}
};
const multiTouchEnd = () => {
lastCenter = null;
lastDist = 0;
}
useEffect(() => {
setHasImageLoaded(false);
if (imageUrl) {
const matchedImg = new window.Image();
matchedImg.src = `${API_URL + imageUrl}`;
matchedImg.onload = () => {
setHasImageLoaded(true);
setImage(matchedImg);
};
}
}, [size, imageUrl]);
let imgDraggable = true
return (
<Stage
width={canvasWidth}
height={canvasHeight}
onWheel={zoom ? handleWheel : null}
scaleX={stageScale}
onTouchMove={handleMultiTouch}
onTouchEnd={() => {
multiTouchEnd()
}}
scaleY={stageScale}
x={stageX}
y={stageY}
>
<Layer>
<Image
x={lastX}
y={lastY}
ref={stageRef}
image={image}
width={imgWidth * alpha || 0}
height={imgHeight * alpha || 0}
onDragMove={(e) => {
if (e.evt.touches.length === 2) {
imgDraggable = false
}
handleImageLastPosition(e)
}}
draggable={imgDraggable}
/>
{rects?.length && hasImageLoaded ?
rects.map((rect) => 
(
<Rectangle
key={uuid()}
x={imageType === 'figure' ? rect.bbox.x0 * alpha + lastX : rect.PN_bbox.x0 * alpha + lastX}
y={imageType === 'figure' ? rect.bbox.y0 * alpha + lastY : rect.PN_bbox.y0 * alpha + lastY}
width={imageType === 'figure' ? rect.bbox.x1 * alpha - rect.bbox.x0 * alpha : rect.PN_bbox.x1 * alpha - rect.PN_bbox.x0 * alpha}
height={imageType === 'figure' ? rect.bbox.y1 * alpha - rect.bbox.y0 * alpha : rect.PN_bbox.y1 * alpha - rect.PN_bbox.y0 * alpha}
rectDraggable={rectDraggable}
borderDash={borderDash}
lastX={lastX}
lastY={lastY}
alpha={alpha}
rect={rect}
fillStatus={clickedRect?.item_no === rect?.item_no}
imageId={imageType === 'table' ? displayedTable?.img_id : displayedFigure?.img_id}
imageType={imageType}
onClickRect={onClickRect}
/>
)
) : null
}
</Layer>
</Stage>
);
};

我试图控制触摸事件长度,因为如果它是2,这意味着它来自多点触摸,所以缩放可以是活动的,反之亦然。然而,它也没有起作用。提前感谢您的帮助。该演示可以在https://codesandbox.io/s/react-konva-zoom-webmobile-demo-em5o6上找到。

你的沙箱代码的问题似乎与这部分:

if (stage.isDragging()) {
stage.stopDrag();
}

首先,它检查STAGE是否被拖拽,而不是图像。但是,即使我们将舞台设置为可拖拽,并开始拖拽,这个检查仍然会返回FALSE,因为我认为它做得太早了。

一个解决方案是添加一个新的状态标志,例如iszoom,并在注册多点触控时将其设置为TRUE。然后我们可以将onDragStart添加到图像道具中,如果isZooming为TRUE,我们在handle函数中运行stage.stopDrag()

这是沙盒示例的修改版本:

https://codesandbox.io/s/react-konva-zoom-webmobile-demo-forked-mb1pg?file=/src/components/generic-canvas.jsx

最新更新