HTML5 canvas Mouseover event - javascript

How do I bind a mouseover or any event for that matter to a drawn object on the canvas? For instance, I tried this:
var c=document.getElementById("myCanvas");
var ctx=c.getContext("2d");
ctx.beginPath();
//STEP ONE
var stepOneRec = ctx.rect(20, 60, 266, 50);
ctx.stroke();
stepOneRec.addEventListener("mouseover", function() { alert('it works!'); });
On one site I looked at it showed a method using Kinetic.js. If that's the only way, I'll use it, I just assume that there's a way to bind events to drawn elements without extra plug-ins. Sorry Canvas noob. I made a fiddle with my code here: http://jsfiddle.net/jyBSZ/2/

(I started this as a posted comment, then realized it's an actual answer.)
Unfortunately, in javascript on it's own, you can't. There are no canvas objects, just the canvas as a whole, and whatever you drew on to its context. Plugins like kinetic can make objects for you, but the whole point of canvas is that the browser can think of it as a single static image.
If you want to, you can bind mousemove events to the canvas, track its position and the position where you drew stuff, and imply on your own that it's over "that object" (effectively what the plugins do), but it's all mousemove events on a single canvas rather than mouseover events on components of it. (You could even make your event binding simulate a mouseover event for the "objects", but underneath, it's still based on checking movement and checking your own object setup.)

The objects drawn within a canvas element are not HTML elements, just pixels, and therefore will not throw DOM events the way that HTML elements would.
You would need to track the locations of your objects yourself and handle the canvas' onmousemove event in order to determine when the mouse is over one of your drawn objects.

you can use jCanvas, take a look here
i made a jsfiddle example for your problem.
just modify next callbacks for desired result
function mouseOut(layer){
$("#mouse-over-text").html('none options selected');
}
function mouseIn(layer){
$("#mouse-over-text").html(getTextForId(layer.name));
}

Related

overlapping Paper.JS (HTML5) Canvas and VivaGraphJS(SVG) Graph: Capturing Events

I've gotten myself into a big and weird position.
I use VivaGraph JS for drawing Conceptual Graphs in the browser. The specific implementation I am using, relies on SVG, and thus my main graph DOM element is an SVG.
During the creation of Edges between nodes, I wrote a small piece of code using Paper.JS which uses canvas from HTML5. In fact, I hacked into the source code provided by vektor.js and simply changed it to listen to CTRL+MouseDown events.
Those two elements, the svg graph and canvas, overlap and have exactly the same dimensions. The graph has nodes and edges manipulated, which listen to mouse and keyboard events, and sadly so does my canvas.
In fact, the reason for using the canvas, was that I wanted to draw a line (a vector or edge or arc) during the mouse-movement, to show to the user what the edge being created would be, before I actually created that edge in the graph.
I could not do this using SVG (Yes, I know, it should be doable) and Paper.js made it extremely easy for me.
Sadly, depending of the order those DOM elements are displayed, either the canvas captures the events, leaving the graph useless, or the graph captures all events, leaving the canvas useless.
Is there some way to add transparency to both DOM elements?
The event listener for the graph, is built into VivaGraphJS, and the event listener for the Paper Vertex, is built into Paper.JS
Ideally, I would like to have the graph on-top, capture the events, and then propagate them back to the canvas, so that the arrows are drawn. I have the feeling that this should be doable, either via pure JavaScript, or by using jQuery.
So far, the events captured in the graph like this:
var graphics = Viva.Graph.View.svgGraphics();
/// some other stuff
graphics.node( function( node )
{
var ui = Viva.Graph.svg('g' ).attr('width', nodeSize )
.attr('height', nodeSize )
// Position the node, add its text, etc, etc...
$( ui ).mousedown( function( event )
{
event.preventDefault();
if ( event.ctrlKey )
{
if ( ctrl_mouse )
{
createEdge( ctrl_mouse, node.id );
ctrl_mouse = null;
// Remove the temporary arrow from canvas - the graph now has a permanent edge
}
else if ( !ctrl_mouse )
{
ctrl_mouse = node.id;
// Start drawing a temporary arrow on canvas
}
}
All this takes place in one file graph.js
In another file edge.js, I setup the event listeners and the way the vector is drawn. I've added it into jsfiddle but sadly it won't run there (I am guessing the keyboard events may not be propagated properly?).
The problem is that Paper.Js has its own event listeners:
function onMouseDown( event )
function onKeyUp( event )
function onMouseMove(event)
Obviously those events have their equivalent in pure JavaScript and jQuery, but the ones I capture in VivaGraphJS or jQuery cannot be propagated to PaperJS, since they are different objects.
So, can I somehow (preferably by using pure JavaScript, but jQuery will also work) emulate or send those events to Paper.JS?
Since nobody answered, and I am guessing this is due to the very specific nature of my question, I stumbled upon the correct answer on another post here in stack overflow. Weirdly enough it was not accepted as the correct answer.
The poster suggested the following:
quickDelegate = function(event, target) {
var eventCopy = document.createEvent("MouseEvents");
eventCopy.initMouseEvent(event.type, event.bubbles, event.cancelable, event.view, event.detail,
event.pageX || event.layerX, event.pageY || event.layerY, event.clientX, event.clientY, event.ctrlKey, event.altKey,
event.shiftKey, event.metaKey, event.button, event.relatedTarget);
target.dispatchEvent(eventCopy);
// ... and in webkit I could just dispath the same event without copying it. eh.
};
This worked for me, and it was the only thing that worked. I tried other libraries I found on github which supposedly forward events, but they didn't work.

How to copy a kineticjs stage to another canvas

I am writing an app where there may be hundreds of canvasses on a page. Rather than having the overhead of an individual stage for each canvas, I decided to have an editor that holds a stage. Once editing is completed it should copy the stage content to another canvas.
Stage offers toImage and toDataURL to get hold of the content however according this performance test, both those methods will be very slow compared to context.drawImage.
See: http://jsperf.com/copying-a-canvas-element
Since I only use one layer in a stage and a layer holds a canvas, I thought I could do this:
desticationCtx.drawImage(layer.getContext().canvas, 0,0);
unfortunately that doesn't produce any results (It does run though)
Since a stage has a bufferCanvas I also tried:
destinationCtx.drawImage(this.stage.bufferCanvas.element,0,0);
Again no results although I can see the content on the stage canvas on the page.
However if I dig down in my page to get to the actual canvas created and used by kineticjs:
destinationCtx.drawImage(document.getElementById('mydiv').children[0].children[0],0,0);
I do get results. What is the proper way to copy kineticjs stage content to another canvas?
access a layer canvas element like this:
var canvasElement = layer.getCanvas().getElement();
and the context like this:
var context = layer.getCanvas().getContext();
Here's the docs of interest:
http://kineticjs.com/docs/Kinetic.Layer.html#getCanvas
http://kineticjs.com/docs/Kinetic.Canvas.html
With the last and newer version of kineticjs the method from the as correct marked answer doesn't work anymore.
Nowadays I just used the following approach to get the kineticjs main canvas - with the help of jquery:
var canvas = $('#canvasDiv').find('canvas');
console.log("Canvas: %O", layerCanvas);
Just replace
#canvasDiv
with the ID of your container.

Ideas for rendering HTML *within* Raphael (SVG/VML) shapes

I'm working on an application that uses Raphael to draw primitive shapes (rectangles, ellipses, triangles etc) and lines but allows the user to move/resize these objects as well. One of the main requirements is that the face of shapes can have formatted text. The actual text is a subset of Markdown (simple things like bolding, italics, lists) and is rendered as HTML.
FWIW - I'm using Backbone.js views to modularize the shape logic.
Approach 1
My initial thought was to use a combination of foreignObject for SVG and direct HTML with VML for IE. However, IE9 doesn't support foreignObject, and therefore this approach had to be abandoned.
Approach 2
With the beside the canvas object, add divs that contain the actual HTML. Then, position them over the actual shape with a transparent background. I've created a shape view that has references to both the actual Raphael shape and the "overlay" div. There are a couple of problems with this approach:
Using overlay that aren't children of the SVG/VML container feels wrong. Does having this overlay element cause other issues with rendering down the road?
Events that are normally trapped by Raphael (drag, click etc) need to be forwarded from the overlay to the Raphael object. For browsers that support pointer-events, this is easily done:
div.shape-text-overlay {
position: absolute;
background: none;
pointer-events: none;
}
However, other browsers (like IE8 and below) need forwarding of the events:
var forwardedEvents = 'mousemove mousedown mouseup click dblclick mouseover mouseout';
this.$elText.on(forwardedEvents, function(e) {
var original = e.originalEvent;
var event;
if (document.createEvent) {
event = document.createEvent('HTMLEvents');
event.initEvent(e.type, true, true);
}
else {
event = document.createEventObject();
event.eventType = e.type;
}
// FYI - This is the most simplistic approach to event forwarding.
// My real implementation is much larger and uses MouseEvents and UIEvents separately.
event.eventName = e.type;
_.extend(event, original);
if (document.createEvent) {
that.el.node.dispatchEvent(event);
}
else {
that.el.node.fireEvent('on' + event.eventType, event);
}
});
Overlapping shapes cause the text to be overlapped because the text/shapes are on different layers. Although overlapping shapes won't be common, this looks bad:
This approach is what I'm currently using but it just feels like a huge hack.
Approach 3
Almost like Approach 1, but this would involve writing text nodes/VML nodes directly. The biggest problem with this is the amount of manual conversion necessary. Breaking outside of Raphael's API seems like it could cause stability issues with the other Raphael objects.
Question
Has anyone else had to do something similar (rendering HTML inside of SVG/VML)? If so, how did you solve this problem? Were events taken into account?
I built this project (no longer live) using Raphael. What I did is actually abandoned the idea of using HTML inside of SVG because it was just too messy. Instead, I absolutely positioned an HTML layer on top of the SVG layer and moved them around together. When I wanted the HTML to show, I merely faded it in and faded the corresponding SVG object out. If timed and lined up correctly, it's not really noticeable to the untrained eye.
While this may not necessarily be what you're looking for, maybe it will get your mind thinking of new ways to approach your problem! Feel free to look at the JS on that page, as it is unminified ;)
PS, the project is a lead-gathering application. If you just want to see how it works, select "Friend" in the first dropdown and you don't have to provide any info.
Unless another answer can be provided and trump my solution, I have continued with the extra div layer. Forwarding events was the most difficult part (if anyone requires the forwarding events code, I can post it here). Again, the largest downside to this solution is that overlapping shapes will cause their text to overlap above the actual drawn shape.

An HTML5 Canvas Problem

I'm working on a script to do several things. In a nutshell, here's what it needs to do:
Read the coordinates from a page and be able to pop up a box within a specific region.
The pop up box needs to be able to follow the mouse around.
I need to be able to modify the box to look however I want (I was thinking a div container that is set to display:hidden, and then the JS sets the display to block when your mouse is in the specified region).
I need to be able to modify it easily (aka, add and subtract objects and coordinate sets)
I was originally using HTML maps (), and that worked great, until I resized my browser, and the div that I had following the mouse no longer lined up correctly. Something about the offset not working correctly, and I couldn't get it to work correctly, so I switched to an HTML canvas.
And now I've got the coordinates in the canvas correctly, I just can't figure out how to get something to pop up when the mouse is inside of a certain section. Here's my current code:
function drawLines(numbers, color){
//poly [x,y, x,y, x,y.....];
var poly=numbers;
context.fillStyle = color;
context.beginPath();
context.moveTo(poly[0], poly[1]);
for( item=2 ; item < poly.length-1 ; item+=2 )
{context.lineTo( poly[item] , poly[item+1] )};
context.closePath();
context.fill();
}
I've got each region inside of an array, which I then pass to the function one by one. The color was a test, and I can easily get each region to show up as a specified color, but that doesn't solve my problem. Any ideas? Thanks!
Seems strange to jump to canvas over a style issue, but ignoring that...
You could bind mousemove events on the canvas element and then do hit tests on your region to see if the mouse is inside the region.
Doing the hit test efficiently might be tricky depending on the number of regions your testing, but it's definitely doable.
The canvas is just like any other block level element, so the same events apply and are bound in the same way.
Here's one example of mouse events interacting with canvas. In this example, the events are bound to the document, but similar ideas apply.
http://dev.opera.com/articles/view/blob-sallad-canvas-tag-and-javascrip/

html5, adding an eventlistener to a drawn image on canvas

I am experimenting with html5 and I have a little image dropdown, the user selects and image and it draws it to the canvas using drawImage();
I can't seem to figure out how to add an event listener to the newly drawn image on the canvas.
I have tried putting it in a variable like so:
var newImg = ctx.drawImage(myImage, 200, 200);
and then adding an eventlistener to that, but it doesn't seem to work.
newImg.addEventListener('mousedown', onImgClick, false);
What is the correct way to do this.
If you're looking for this sort of interactivity, <canvas> is probably not what you want. You're looking for SVG. There is a fantastic library called Raphaël that makes working with SVG a piece of cake on all browsers, even on IE6. It's also completely compatible with jQuery, so you can do:
var paper = Raphael(10, 50, 320, 200);
var img = paper.image("/path/to/img.png", 10, 10, 80, 80);
$(img).click(onImgClick);
I'm pretty certain this will treat you better and be easier to use than <canvas>.
Note: Raphaël does come with some helpers for basic events like "click" and "mousedown", just do img.click(onImgClick), but if you're already using a library like jQuery, which you probably are, I'd recommend being consistent and using the library's event handling.
Canvas is not a retained-mode drawing surface. It's just a flat image; there is no object inside it to bind events to, and drawImage returns nothing.
You will have to detect clicks on the <canvas> and check if they are inside the [200, 200, 200+w, 200+h] rectangle manually. Or, if you want retained-mode drawing, consider using SVG instead of a canvas.
To do this without the help of JS:
Although you can't attach an event listener to the context on which you call drawImage(); you CAN attach event listeners to the Canvas itself.
myCanvasElement = document.getElementById('canvasElement');
myCanvasElement.addEventListener("click", someFunction, false);
If you need to have this per-image that you draw, you could probably stack the canvas objects and create a new one for each image you draw.

Categories

Resources