检测HTML画布中某些点的鼠标悬停?

时间:2022-10-29 11:18:05

I've built an analytical data visualization engine for Canvas and have been requested to add tooltip-like hover over data elements to display detailed metrics for the data point under the cursor.

我已经为Canvas构建了一个分析数据可视化引擎,并且已经要求在数据元素上添加类似工具提示的悬停,以显示光标下数据点的详细指标。

For simple bar & Gaant charts, tree graphs and node maps with simple square areas or specific points of interest, I was able to implement this by overlaying absolutely-positioned DIVs with :hover attributes, but there are some more complicated visualizations such as pie charts and a traffic flow rendering which has hundreds of separate areas defined by bezeir curves.

对于简单的条形图和Gaant图表,具有简单方形区域或特定兴趣点的树形图和节点图,我能够通过将绝对定位的DIV与:悬停属性重叠来实现这一点,但是还有一些更复杂的可视化,例如饼图以及由bezeir曲线定义的数百个独立区域的交通流渲染。

Is is possible to somehow attach an overlay, or trigger an event when the user mouses over a specific closed path?

是否有可能以某种方式附加叠加层,或在用户将鼠标悬停在特定的封闭路径上时触发事件?

Each area for which hover needs to be specified is defined as follows:

需要指定悬停的每个区域定义如下:

context.beginPath();
context.moveTo(segmentRight, prevTop);
context.bezierCurveTo(segmentRight, prevTop, segmentLeft, thisTop, segmentLeft, thisTop);
context.lineTo(segmentLeft, thisBottom);
context.bezierCurveTo(segmentLeft, thisBottom, segmentRight, prevBottom, segmentRight, prevBottom);
/*
 * ...define additional segments...
 */
// <dream> Ideally I would like to attach to events on each path:
context.setMouseover(function(){/*Show hover content*/});
// </dream>
context.closePath();

Binding to an object like this is almost trivial to implement in Flash or Silverlight, since but the current Canvas implementation has the advantage of directly using our existing Javascript API and integrating with other Ajax elements, we are hoping to avoid putting Flash into the mix.

绑定到这样的对象在Flash或Silverlight中实现几乎是微不足道的,因为当前的Canvas实现具有直接使用我们现有的Javascript API并与其他Ajax元素集成的优势,我们希望避免将Flash置于混合中。

Any ideas?

7 个解决方案

#1


21  

You could handle the mousemove event and get the x,y coordinates from the event. Then you'll probably have to iterate over all your paths to test if the point is over the path. I had a similar problem that might have some code you could use.

您可以处理mousemove事件并从事件中获取x,y坐标。然后,您可能必须迭代所有路径以测试该点是否在路径上。我有一个类似的问题,可能有一些你可以使用的代码。

Looping over things in this way can be slow, especially on IE. One way you could potentially speed it up - and this is a hack, but it would be quite effective - would be to change the color that each path is drawn with so that it is not noticeable by humans but so that each path is drawn in a different color. Have a table to look up colors to paths and just look up the color of the pixel under the mouse.

以这种方式循环事物可能会很慢,特别是在IE上。有一种方法你可以加速它 - 这是一个黑客,但它会非常有效 - 将改变每个路径绘制的颜色,以便人类不会注意到但是每个路径都被绘制成不同的颜色。有一个表来查找路径的颜色,只需查看鼠标下的像素颜色。

#2


13  

Shadow Canvas

The best method I have seen elsewhere for mouseover detection is to repeat the part of your drawing that you want to detect onto a hidden, cleared canvas. Then store the ImageData object. You can then check the ImageData array for the pixel of interest and return true if the alpha value is greater than 0.

我在其他地方看到的用于鼠标悬停检测的最佳方法是将要检测的图形部分重复到隐藏的清除画布上。然后存储ImageData对象。然后,您可以检查ImageData数组中的感兴趣像素,如果alpha值大于0,则返回true。

// slow part
ctx.clearRect(0,0,canvas.width,canvas.height);
ctx.fillRect(100,100,canvas.width-100,canvas.height-100);
var pixels = ctx.getImageData(0,0,canvas.width,canvas.height).data;

// fast part
var idx = 4 * (mouse_x + mouse_y * canvas.width) + 3;
if (pixels[idx]) { // alpha > 0
  ...
}

Advantages

  • You can detect anything you want since you're just repeating the context methods. This works with PNG alpha, crazy compound shapes, text, etc.
  • 您可以检测任何您想要的内容,因为您只是重复上下文方法。这适用于PNG alpha,疯狂的复合形状,文本等。

  • If your image is fairly static, then you only need to do this one time per area of interest.
  • 如果您的图像是相当静态的,那么您只需要每个感兴趣的区域执行一次此操作。

  • The "mask" is slow, but looking up the pixel is dirt cheap. So the "fast part" is great for mouseover detection.
  • “面具”很慢,但查找像素是便宜的。因此,“快速部分”非常适合鼠标悬停检测。

Disadvantages

  • This is a memory hog. Each mask is W*H*4 values. If you have a small canvas area or few areas to mask, it's not that bad. Use chrome's task manager to monitor memory usage.
  • 这是一个记忆猪。每个掩码是W * H * 4值。如果你有一个小的帆布区域或几个区域来掩盖,它并没有那么糟糕。使用chrome的任务管理器来监控内存使用情况。

  • There is currently a known issue with getImageData in Chrome and Firefox. The results are not garbage collected right away if you nullify the variable, so if you do this too frequently, you will see memory rise rapidly. It does eventually get garbage collected and it shouldn't crash the browser, but it can be taxing on machines with small amounts of RAM.
  • 目前Chrome和Firefox中存在getImageData的已知问题。如果你使变量无效,结果不会立即收集垃圾,所以如果你经常这样做,你会看到内存迅速上升。它最终会收集垃圾并且它不会使浏览器崩溃,但它可能会对具有少量RAM的计算机造成负担。

A Hack to Save Memory

拯救记忆的黑客

Rather than storing the whole ImageData array, we can just remember which pixels have alpha values. It saves a great deal of memory, but adds a loop to the mask process.

我们可以只记住哪些像素具有alpha值,而不是存储整个ImageData数组。它节省了大量内存,但为掩码进程添加了一个循环。

var mask = {};
var len = pixels.length;
for (var i=3;i<len;i+=4) if ( pixels[i] ) mask[i] = 1;

// this works the same way as the other method
var idx = 4 * (mouse_x + mouse_y * canvas.width) + 3;
if (mask[idx]) {
  ...
}

#3


7  

This could be done using the method ctx.isPointInPath, but it is not implemented in ExCanvas for IE. But another solution would be to use HTML maps, like I did for this little library : http://phenxdesign.net/projects/phenx-web/graphics/example.htm you can get inspiration from it, but it is still a little buggy.

这可以使用方法ctx.isPointInPath完成,但它不是在ExCanvas for IE中实现的。但另一种解决方案是使用HTML地图,就像我为这个小库所做的那样:http://phenxdesign.net/projects/phenx-web/graphics/example.htm你可以从中获得灵感,但它仍然是一点点越野车。

#4


1  

I would suggest overlaying an image map with proper coordinates set on the areas to match your canvas-drawn items. This way, you get tooltips AND a whole lot of other DOM/Browser functionality for free.

我建议覆盖一个图像地图,并在区域上设置适当的坐标,以匹配画布绘制的项目。这样,您可以免费获得工具提示和许多其他DOM /浏览器功能。

#5


1  

There is a book by Eric Rowell named "HTML5 CANVAS COOKBOOK". In that book there is a chapter named "Interacting with the Canvas: Attaching Event Listeners to Shapes and Regions". mousedown, mouseup, mouseover, mouseout, mousemove, touchstart, touchend and touchmove events can be implemented. I highly suggest you read that.

Eric Rowell有一本名为“HTML5 CANVAS COOKBOOK”的书。在那本书中有一章名为“与画布交互:将事件监听器附加到形状和区域”。可以实现mousedown,mouseup,mouseover,mouseout,mousemove,touchstart,touchend和touchmove事件。我强烈建议你阅读。

#6


0  

This can't be done (well, at least not that easily), because objects you draw on the canvas (paths) are not represented as the same objects in the canvas. What I mean is that it is just a simple 2D context and once you drawn something on it, it completely forgets how it was drawn. It is just a set of pixels for it.

这是不可能的(好吧,至少不那么容易),因为在画布上绘制的对象(路径)不会在画布中表示为相同的对象。我的意思是它只是一个简单的2D上下文,一旦你在它上面画了一些东西,它就完全忘记了它的绘制方式。它只是一组像素。

In order to watch mouseover and the likes for it, you need some kind of vector graphics canvas, that is SVG or implement your own on top of existing (which is what Sam Hasler suggested)

为了观看鼠标悬停和它的喜欢,你需要某种矢量图形画布,即SVG或在现有的基础上实现你自己(这是Sam Hasler建议的)

#7


0  

I needed to do detect mouse clicks for a grid of squares (like cells of an excel spreadsheet). To speed it up, I divided the grid into regions recursively halving until a small number of cells remained, for example for a 100x100 grid, the first 4 regions could be the 50x50 grids comprising the four quadrants. Then these could be divided into another 4 each (hence giving 16 regions of 25x25 each). This requires a small number of comparisons and finally the 25x25 grid could be tested for each cell (625 comparisons in this example).

我需要检测鼠标点击方块网格(如excel电子表格的单元格)。为了加快速度,我将网格划分为递归减半的区域,直到剩下少量单元格,例如100x100网格,前4个区域可以是包含四个象限的50x50网格。然后这些可以分成另外4个(因此给出16个区域,每个25x25)。这需要进行少量比较,最后可以针对每个单元测试25x25网格(在该示例中为625个比较)。

#1


21  

You could handle the mousemove event and get the x,y coordinates from the event. Then you'll probably have to iterate over all your paths to test if the point is over the path. I had a similar problem that might have some code you could use.

您可以处理mousemove事件并从事件中获取x,y坐标。然后,您可能必须迭代所有路径以测试该点是否在路径上。我有一个类似的问题,可能有一些你可以使用的代码。

Looping over things in this way can be slow, especially on IE. One way you could potentially speed it up - and this is a hack, but it would be quite effective - would be to change the color that each path is drawn with so that it is not noticeable by humans but so that each path is drawn in a different color. Have a table to look up colors to paths and just look up the color of the pixel under the mouse.

以这种方式循环事物可能会很慢,特别是在IE上。有一种方法你可以加速它 - 这是一个黑客,但它会非常有效 - 将改变每个路径绘制的颜色,以便人类不会注意到但是每个路径都被绘制成不同的颜色。有一个表来查找路径的颜色,只需查看鼠标下的像素颜色。

#2


13  

Shadow Canvas

The best method I have seen elsewhere for mouseover detection is to repeat the part of your drawing that you want to detect onto a hidden, cleared canvas. Then store the ImageData object. You can then check the ImageData array for the pixel of interest and return true if the alpha value is greater than 0.

我在其他地方看到的用于鼠标悬停检测的最佳方法是将要检测的图形部分重复到隐藏的清除画布上。然后存储ImageData对象。然后,您可以检查ImageData数组中的感兴趣像素,如果alpha值大于0,则返回true。

// slow part
ctx.clearRect(0,0,canvas.width,canvas.height);
ctx.fillRect(100,100,canvas.width-100,canvas.height-100);
var pixels = ctx.getImageData(0,0,canvas.width,canvas.height).data;

// fast part
var idx = 4 * (mouse_x + mouse_y * canvas.width) + 3;
if (pixels[idx]) { // alpha > 0
  ...
}

Advantages

  • You can detect anything you want since you're just repeating the context methods. This works with PNG alpha, crazy compound shapes, text, etc.
  • 您可以检测任何您想要的内容,因为您只是重复上下文方法。这适用于PNG alpha,疯狂的复合形状,文本等。

  • If your image is fairly static, then you only need to do this one time per area of interest.
  • 如果您的图像是相当静态的,那么您只需要每个感兴趣的区域执行一次此操作。

  • The "mask" is slow, but looking up the pixel is dirt cheap. So the "fast part" is great for mouseover detection.
  • “面具”很慢,但查找像素是便宜的。因此,“快速部分”非常适合鼠标悬停检测。

Disadvantages

  • This is a memory hog. Each mask is W*H*4 values. If you have a small canvas area or few areas to mask, it's not that bad. Use chrome's task manager to monitor memory usage.
  • 这是一个记忆猪。每个掩码是W * H * 4值。如果你有一个小的帆布区域或几个区域来掩盖,它并没有那么糟糕。使用chrome的任务管理器来监控内存使用情况。

  • There is currently a known issue with getImageData in Chrome and Firefox. The results are not garbage collected right away if you nullify the variable, so if you do this too frequently, you will see memory rise rapidly. It does eventually get garbage collected and it shouldn't crash the browser, but it can be taxing on machines with small amounts of RAM.
  • 目前Chrome和Firefox中存在getImageData的已知问题。如果你使变量无效,结果不会立即收集垃圾,所以如果你经常这样做,你会看到内存迅速上升。它最终会收集垃圾并且它不会使浏览器崩溃,但它可能会对具有少量RAM的计算机造成负担。

A Hack to Save Memory

拯救记忆的黑客

Rather than storing the whole ImageData array, we can just remember which pixels have alpha values. It saves a great deal of memory, but adds a loop to the mask process.

我们可以只记住哪些像素具有alpha值,而不是存储整个ImageData数组。它节省了大量内存,但为掩码进程添加了一个循环。

var mask = {};
var len = pixels.length;
for (var i=3;i<len;i+=4) if ( pixels[i] ) mask[i] = 1;

// this works the same way as the other method
var idx = 4 * (mouse_x + mouse_y * canvas.width) + 3;
if (mask[idx]) {
  ...
}

#3


7  

This could be done using the method ctx.isPointInPath, but it is not implemented in ExCanvas for IE. But another solution would be to use HTML maps, like I did for this little library : http://phenxdesign.net/projects/phenx-web/graphics/example.htm you can get inspiration from it, but it is still a little buggy.

这可以使用方法ctx.isPointInPath完成,但它不是在ExCanvas for IE中实现的。但另一种解决方案是使用HTML地图,就像我为这个小库所做的那样:http://phenxdesign.net/projects/phenx-web/graphics/example.htm你可以从中获得灵感,但它仍然是一点点越野车。

#4


1  

I would suggest overlaying an image map with proper coordinates set on the areas to match your canvas-drawn items. This way, you get tooltips AND a whole lot of other DOM/Browser functionality for free.

我建议覆盖一个图像地图,并在区域上设置适当的坐标,以匹配画布绘制的项目。这样,您可以免费获得工具提示和许多其他DOM /浏览器功能。

#5


1  

There is a book by Eric Rowell named "HTML5 CANVAS COOKBOOK". In that book there is a chapter named "Interacting with the Canvas: Attaching Event Listeners to Shapes and Regions". mousedown, mouseup, mouseover, mouseout, mousemove, touchstart, touchend and touchmove events can be implemented. I highly suggest you read that.

Eric Rowell有一本名为“HTML5 CANVAS COOKBOOK”的书。在那本书中有一章名为“与画布交互:将事件监听器附加到形状和区域”。可以实现mousedown,mouseup,mouseover,mouseout,mousemove,touchstart,touchend和touchmove事件。我强烈建议你阅读。

#6


0  

This can't be done (well, at least not that easily), because objects you draw on the canvas (paths) are not represented as the same objects in the canvas. What I mean is that it is just a simple 2D context and once you drawn something on it, it completely forgets how it was drawn. It is just a set of pixels for it.

这是不可能的(好吧,至少不那么容易),因为在画布上绘制的对象(路径)不会在画布中表示为相同的对象。我的意思是它只是一个简单的2D上下文,一旦你在它上面画了一些东西,它就完全忘记了它的绘制方式。它只是一组像素。

In order to watch mouseover and the likes for it, you need some kind of vector graphics canvas, that is SVG or implement your own on top of existing (which is what Sam Hasler suggested)

为了观看鼠标悬停和它的喜欢,你需要某种矢量图形画布,即SVG或在现有的基础上实现你自己(这是Sam Hasler建议的)

#7


0  

I needed to do detect mouse clicks for a grid of squares (like cells of an excel spreadsheet). To speed it up, I divided the grid into regions recursively halving until a small number of cells remained, for example for a 100x100 grid, the first 4 regions could be the 50x50 grids comprising the four quadrants. Then these could be divided into another 4 each (hence giving 16 regions of 25x25 each). This requires a small number of comparisons and finally the 25x25 grid could be tested for each cell (625 comparisons in this example).

我需要检测鼠标点击方块网格(如excel电子表格的单元格)。为了加快速度,我将网格划分为递归减半的区域,直到剩下少量单元格,例如100x100网格,前4个区域可以是包含四个象限的50x50网格。然后这些可以分成另外4个(因此给出16个区域,每个25x25)。这需要进行少量比较,最后可以针对每个单元测试25x25网格(在该示例中为625个比较)。