将 canvas 个对象拖放到可缩放的 SVG 图像地图上
Drag and drop canvas objects onto scalable SVG image map
我正在寻找有关如何进行 ui 设计的建议。
我可能走错了路(我有疑问),但我正在尝试将这两个概念结合起来:
我需要图像映射的 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、分层区域、拖放、响应式缩放以及确定对象何时位于区域内。
我正在寻找有关如何进行 ui 设计的建议。
我可能走错了路(我有疑问),但我正在尝试将这两个概念结合起来:
我需要图像映射的 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、分层区域、拖放、响应式缩放以及确定对象何时位于区域内。