将 canvas 个对象拖放到可缩放的 SVG 图像地图上

Drag and drop canvas objects onto scalable SVG image map

我正在寻找有关如何进行 ui 设计的建议。

我可能走错了路(我有疑问),但我正在尝试将这两个概念结合起来:

Drag and Drop Canvas

SVG Image Map

我需要图像映射的 equivalent,然后需要能够实例化然后将对象拖动到特定区域(定义为 SVG(多边形))。当对象被放入区域时,我需要能够(引发事件)捕获它们所在的区域(或没有区域)以及对象的坐标,以便它们可以持久保存在数据库中。整个事情应该是响应式的。

我已经开始 JsFiddle,但是在使用 Z 索引将 SVG 放在 canvas 下时有点碰壁。不确定如何混合两者(如果这是正确的方法。)

如有任何意见或建议,我们将不胜感激。也许有一个现有的 library/framwork 可以解决这个问题?

JsFiddle
HTML:

<div style="position: absolute;">
  <canvas id="canvas" width="600" height="600" style="z-index: 2;"></canvas>

  <div id="artwork">
  <svg version="1.1" id="MAP" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
     viewBox="0 0 640 480" enable-background="new 0 0 640 480" xml:space="preserve">
  <circle id="C3" fill="none" stroke="#000000" stroke-miterlimit="10" cx="169" cy="204.333" r="53.333"/>
  <circle id="C2" fill="none" stroke="#000000" stroke-miterlimit="10" cx="317.667" cy="345.667" r="49.333"/>
  <circle id="C1" fill="none" stroke="#000000" stroke-miterlimit="10" cx="397.333" cy="113.333" r="47.667"/>
  <rect id="R1" x="74.333" y="26.333" fill="none" stroke="#000000" stroke-miterlimit="10" width="103.333" height="103.333"/>
  <polygon id="P1" fill="none" stroke="#000000" stroke-miterlimit="10" points="488.333,194.333 519.667,302.333 483,365.667 
    469.667,289.667 387,281.667 "/>
    </svg>

  </div>
</div>

JS:

let paths = Array.from(document.getElementsByTagName("polygon"));
paths.forEach((path) => {
  path.addEventListener("click", (e) => {
    console.log("hi");
    console.log(e.screenX);
    // do something with e.target
  });
});

var Rectangle = function (x, y, width, height) {
  this.x = x;
  this.y = y;
  this.width = width;
  this.height = height;
  this.isDragging = false;

  this.render = function (ctx) {
    ctx.save();

    ctx.beginPath();
    ctx.rect(
      this.x - this.width * 0.5,
      this.y - this.height * 0.5,
      this.width,
      this.height
    );
    ctx.fillStyle = "rgba(0,0,200,.5)";//#2793ef";
    ctx.fill();

    ctx.restore();
  };
};

var Arc = function (x, y, radius, radians) {
  this.x = x;
  this.y = y;
  this.radius = radius;
  this.radians = radians;
  this.isDragging = false;

  this.render = function (ctx) {
    ctx.save();

    ctx.beginPath();
    ctx.arc(this.x, this.y, this.radius, 0, this.radians, false);
    ctx.fillStyle = "rgba(0,200,200,.5)";//"#2793ef";
    ctx.fill();

    ctx.restore();
  };
};

var MouseTouchTracker = function (canvas, callback) {
  function processEvent(evt) {
    var rect = canvas.getBoundingClientRect();
    var offsetTop = rect.top;
    var offsetLeft = rect.left;

    if (evt.touches) {
      return {
        x: evt.touches[0].clientX - offsetLeft,
        y: evt.touches[0].clientY - offsetTop
      };
    } else {
      return {
        x: evt.clientX - offsetLeft,
        y: evt.clientY - offsetTop
      };
    }
  }

  function onDown(evt) {
    evt.preventDefault();
    var coords = processEvent(evt);
    callback("down", coords.x, coords.y);
  }

  function onUp(evt) {
    evt.preventDefault();
    callback("up");
  }

  function onMove(evt) {
    evt.preventDefault();
    var coords = processEvent(evt);
    callback("move", coords.x, coords.y);
  }

  canvas.ontouchmove = onMove;
  canvas.onmousemove = onMove;

  canvas.ontouchstart = onDown;
  canvas.onmousedown = onDown;
  canvas.ontouchend = onUp;
  canvas.onmouseup = onUp;
};

function isHit(shape, x, y) {
  if (shape.constructor.name === "Arc") {
    var dx = shape.x - x;
    var dy = shape.y - y;
    if (dx * dx + dy * dy < shape.radius * shape.radius) {
      return true;
    }
  } else {
    if (
      x > shape.x - shape.width * 0.5 &&
      y > shape.y - shape.height * 0.5 &&
      x < shape.x + shape.width - shape.width * 0.5 &&
      y < shape.y + shape.height - shape.height * 0.5
    ) {
      return true;
    }
  }

  return false;
}

var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
var startX = 0;
var startY = 0;

var rectangle = new Rectangle(50, 50, 100, 100);
rectangle.render(ctx);

var circle = new Arc(200, 140, 50, Math.PI * 2);
circle.render(ctx);

var mtt = new MouseTouchTracker(canvas, function (evtType, x, y) {
  ctx.clearRect(0, 0, canvas.width, canvas.height);

  switch (evtType) {
    case "down":
      startX = x;
      startY = y;
      if (isHit(rectangle, x, y)) {
        rectangle.isDragging = true;
      }
      if (isHit(circle, x, y)) {
        circle.isDragging = true;
      }
      break;

    case "up":
      rectangle.isDragging = false;
      circle.isDragging = false;
      break;

    case "move":
      var dx = x - startX;
      var dy = y - startY;
      startX = x;
      startY = y;

      if (rectangle.isDragging) {
        rectangle.x += dx;
        rectangle.y += dy;
      }

      if (circle.isDragging) {
        circle.x += dx;
        circle.y += dy;
      }
      break;
  }

  circle.render(ctx);
  rectangle.render(ctx);
});

var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
var startX = 0;
var startY = 0;

var rectangle = new Rectangle(50, 50, 100, 100);
rectangle.render(ctx);

var circle = new Arc(200, 140, 50, Math.PI * 2);
circle.render(ctx);

var mtt = new MouseTouchTracker(canvas, function (evtType, x, y) {
  ctx.clearRect(0, 0, canvas.width, canvas.height);

  switch (evtType) {
    case "down":
      startX = x;
      startY = y;
      if (isHit(rectangle, x, y)) {
        rectangle.isDragging = true;
      }
      if (isHit(circle, x, y)) {
        circle.isDragging = true;
      }
      break;

    case "up":
      rectangle.isDragging = false;
      circle.isDragging = false;
      break;

    case "move":
      var dx = x - startX;
      var dy = y - startY;
      startX = x;
      startY = y;

      if (rectangle.isDragging) {
        rectangle.x += dx;
        rectangle.y += dy;
      }

      if (circle.isDragging) {
        circle.x += dx;
        circle.y += dy;
      }
      break;
  }

  circle.render(ctx);
  rectangle.render(ctx);
});

CSS:

<style>
#artwork{
  background: url(artwork.jpg);
  background-size: cover;
  position: absolute;

}
#artwork:after{
  content: '';
  display: block;
  padding-bottom: 100%;
 
}

#artwork svg{

  position: absolute; 
  left: 0px; 
  top:0px;
  z-index: -1;
  width: 600px;
}

#artwork svg circle{
  cursor: pointer;
  fill: rgba(255, 0, 255, 1);
}

#artwork svg polygon{
  cursor: pointer;
  fill: rgba(0, 255, 255, 1);
}

</style>

我使用 Konva JS 库解决了这个问题。它处理绘制 SVG、分层区域、拖放、响应式缩放以及确定对象何时位于区域内。