简体   繁体   English

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

[英]Detect mouseover of certain points within an HTML canvas?

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? 有任何想法吗?

You could handle the mousemove event and get the x,y coordinates from the event. 您可以处理mousemove事件并从事件中获取x,y坐标。 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. 我有一个类似的问题 ,可能有一些你可以使用的代码。

Looping over things in this way can be slow, especially on IE. 以这种方式循环事物可能会很慢,尤其是在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. 有一个表来查找路径的颜色,只需查看鼠标下的像素颜色。

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. 然后存储ImageData对象。 You can then check the ImageData array for the pixel of interest and return true if the alpha value is greater than 0. 然后,您可以检查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. 每个掩模是W * H * 4值。 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. 使用chrome的任务管理器来监控内存使用情况。
  • There is currently a known issue with getImageData in Chrome and Firefox. 目前Chrome和Firefox中存在getImageData的已知问题。 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. 它最终会收集垃圾并且不会使浏览器崩溃,但它可能会在具有少量RAM的计算机上产生负担。

A Hack to Save Memory 拯救记忆的黑客

Rather than storing the whole ImageData array, we can just remember which pixels have alpha values. 我们可以只记住哪些像素具有alpha值,而不是存储整个ImageData数组。 It saves a great deal of memory, but adds a loop to the mask process. 它节省了大量内存,但为掩码进程添加了一个循环。

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]) {
  ...
}

This could be done using the method ctx.isPointInPath, but it is not implemented in ExCanvas for IE. 这可以使用方法ctx.isPointInPath完成,但它不是在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. 但另一个解决方案是使用HTML地图,就像我为这个小库所做的那样: http//phenxdesign.net/projects/phenx-web/graphics/example.htm你可以从中获得灵感,但它仍然是一点点越野车。

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 /浏览器功能。

I needed to do detect mouse clicks for a grid of squares (like cells of an excel spreadsheet). 我需要检测鼠标点击方块的网格(如excel电子表格的单元格)。 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. 为了加快速度,我将网格划分为递归减半的区域,直到剩余少量单元格,例如100x100网格,前4个区域可以是包含四个象限的50x50网格。 Then these could be divided into another 4 each (hence giving 16 regions of 25x25 each). 然后可以将它们分成另外4个(因此给出16个区域,每个区域25x25)。 This requires a small number of comparisons and finally the 25x25 grid could be tested for each cell (625 comparisons in this example). 这需要进行少量比较,最后可以针对每个单元测试25x25网格(在该示例中为625个比较)。

There is a book by Eric Rowell named "HTML5 CANVAS COOKBOOK". Eric Rowell有一本名为“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. 可以实现mousedown,mouseup,mouseover,mouseout,mousemove,touchstart,touchend和touchmove事件。 I highly suggest you read that. 我强烈建议你阅读。

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. 我的意思是它只是一个简单的2D上下文,一旦你在它上面画了一些东西,它就会完全忘记它的绘制方式。 It is just a set of pixels for it. 它只是一组像素。

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建议的)

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM