假设我有一个html5 / canvas应用程序,我可以在画布上放置对象。某种图编辑器,比如Visio(但更简单)
有没有一个框架可以帮助我找到被点击/拖动的对象?
一个选项是捕获click事件并迭代我的所有对象(以半智能的方式)并检查它是否被点击/拖动,但我讨厌重新发明轮子:)
简短的版本是Canvas本身没有跟踪这些。
如果您不想重新发明轮子,请从教程"Making and Moving Selectable Shapes on an HTML5 Canvas: A Simple Example."中的示例代码开始。它为单击和拖动提供了一个很好的介绍和平台。
对于Visio风格的应用程序,或任何与绘制对象的交互很重要的事情,你最好使用SVG。已经有some open source projects可以作为起点。 SVG的问题是,与任何DOM重物一样,一旦你有大量的对象需要操作,性能就会急剧下降。
遗憾的是,您没有渲染到画布中的对象模型。当您检测到点击,鼠标,mousedown,拖动时,您需要跟踪您绘制的所有内容并触发单独的事件。这意味着为所有上下文方法创建一个包装器,将行/图像及其属性存储到COM(Canvas对象模型:)中,并为每个行/图像触发事件
我没有看到任何事情做到这一点。这可能是很多工作,每次我需要为Canvas添加交互性时,我都会编写自定义代码
你可以使用ctx.isPointInPath()
方法很容易地做到这一点。它以x
坐标和y
坐标的形式获取一个点,并检查画布中是否存在包含该点内的某个点的路径。
那么,你怎么能用它来弄清楚那个点落在哪个元素之内呢?好吧,当画布被清除时,画布上显然没有任何点,因此ctx.isPointInPath()
将始终返回false
。然后,我们开始逐个将每个元素添加到画布。在画布上绘制元素之后,我们检查函数 - 如果它返回false
,那么该点不在该元素内,所以我们继续。
由于我们一直在检查每个元素,因为画布是空白的,我们知道这一点不可能在我们已经检查过的元素中。因此,当我们最终找到一个返回true
的元素时,我们知道该点必须在该特定元素内。
这里很好的部分是我们不必编写任何自定义代码来尝试检测点是否在形状内,这可能很棘手,特别是如果形状很复杂 - 这都是由浏览器处理的。
这种方法存在一个问题 - 如果该点在两个或更多形状内,那么它将仅报告它在第一个形状内。一个解决方案可能是拥有一个单独的隐藏画布并循环遍历所有形状,在每个形状之后清除它,并在数组中存储ctx.isPointInPath()
返回true的那些名称。您可以采取其他方法。在这个答案中,我更多地关注基本思想,而不是像这样的特殊情况。
下面我有一个这种方法的例子。我已经设置了一个画布,一个跟踪鼠标位置的脚本,一个对象列表,每个对象都有一个名称,还有一个绘制功能,可以将它绘制到画布上。你可以想象一个以对象为导向的方法,它只是一个对象列表,它们都扩展了一些AbstractShape
类,但我为演示保留了简单的东西。
然后,我只是不断地重绘画布(就像你动画它,制作游戏等一样),每次使用我上面定义的算法来检查鼠标指针是否处于那种形状。
//Define the objects to draw on the canvas
let objects = [
{"name":"Circle", "draw":()=>{
ctx.beginPath();
ctx.arc(200, 75, 50, 0, 2 * Math.PI);
ctx.fill();
ctx.closePath();
}},
{"name":"Square", "draw":()=>{
ctx.rect(10, 10, 100, 100);
ctx.fill();
}}
];
//Keep track of the mouse pointer
let mouseX, mouseY;
document.body.addEventListener("mousemove", (event)=>{
mouseX = event.clientX;
mouseY = event.clientY;
});
//Find the canvas, its context, and the output element
let cv = document.getElementById("cv");
let ctx = cv.getContext("2d");
let output = document.getElementById("output");
//Draw the objects on the canvas
function draw() {
//Clear the canvas
ctx.clearRect(0, 0, cv.width, cv.height);
//Clear the output div
output.innerHTML = "";
//This variable is true until we find an object
let noObjectFound = true;
//Loop through all of the objects
for (let object of objects) {
//Draw the current object
object.draw();
//Check if the mouse is on top of it
if (noObjectFound && ctx.isPointInPath(mouseX, mouseY)) {
//Update the output div
output.innerHTML = object.name;
noObjectFound = false;
}
}
//Call again ASAP
window.requestAnimationFrame(draw);
}
window.requestAnimationFrame(draw);
body {
padding:0;
margin:0;
}
#cv {
background-color:#eee;
}
<canvas id="cv"></canvas>
<h1 id="output"></h1>