首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >React-konva移动缩放和可拖动图像

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

Stack Overflow用户
提问于 2021-02-06 18:48:29
回答 1查看 458关注 0票数 0

我正在使用Konva.js构建一个canvas应用程序。这个应用程序允许用户在任何时候拖动和缩放图像。这在Desktop上工作得很好。然而,我喜欢这个应用程序对移动设备的响应。这里的问题是画布缩放和图像拖动不能同步工作。由于“onTouchMove”事件在图像拖动时起作用,因此此应用程序无法按预期工作。

代码语言:javascript
复制
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上找到。

EN

回答 1

Stack Overflow用户

发布于 2021-04-06 21:23:45

沙箱代码中的问题似乎出在这一部分:

代码语言:javascript
复制
if (stage.isDragging()) {
    stage.stopDrag();
  }

首先,它检查的是舞台是否被拖动,而不是图像。但是,即使我们将阶段设置为可拖动并开始拖动,这个检查无论如何也会返回FALSE,因为我认为它完成得太早了。

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

以下是您的沙箱示例的修改版本:

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

票数 -1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/66075989

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档