0

I'm trying to get my jQuery event callback to trigger correctly, but I can't seem to get around the fact the element I am interested in not receiving the event because of another element that covers it on the page. I can summarise it as follows (I've styled the elements so they show up in a jsfiddle):

<div id='mydiv'>
    <div style="border: 1px solid; border-color: red; position: absolute; left: 0px; top: 0px; width: 200px; height: 200px; z-index: 100">Hello</div>
    <canvas style="border: 1px solid; border-color: yellow; position: absolute; left: 50px; top: 50px; width: 150px; height: 150px"></canvas>
</div>​

With the segment above, if I try to listen to mouse clicks on the <canvas>, the event never gets called:

$('#mydiv').on('mousedown', 'canvas', this, function(ev) {
    console.log(ev.target);
});

However, if I modify my event handler to listen to the <div> element instead, the callback is triggered as expected:

$('#mydiv').on('mousedown', 'div', this, function(ev) {
    console.log(ev.target);
});

How can I coerce my <canvas> to receive events, whilst leaving the offending <div> block in the fore-front?

seanhodges
  • 17,426
  • 15
  • 71
  • 93

4 Answers4

1

You can't. Objects on top get the click events. That's how the DOM works.

If you want to handle that click event, you will need to handle in the object that is on top or use bubbling and handle it in a parent object. You can handle it in the top object and "forward" it to the other object if you want by triggering a click on that other object or by just calling a click handler directly.

Or, you can move the canvas element above the ul by setting it's z-index to a higher value and it will then get the click event.

Or, you can make a new transparent canvas object that is on top that gets the event, leaving the other two objects where they are for the desired visual effect.

jfriend00
  • 683,504
  • 96
  • 985
  • 979
  • Great, thanks for the explanation. I'll first try forwarding the event to the buried element, since I have little control over the rendering of this particular layout. – seanhodges Feb 26 '12 at 15:46
  • I couldn't get the event forwarding to work as I needed the pageX/pageY values, passing the event variable just messed up the event handler. I'll try some of the other answers here, as well as your transparent canvas suggestion... – seanhodges Feb 27 '12 at 10:26
1

You can bind the element to the closest common parent, and check whether the X and Y coordinates of the mouse are within the range of the canvas.

  • In the example below, I have cached the dimensions (height and width) of the canvas, because I assume these to be constant. Move this inside the function if the dimensions are not constant.

  • I use the .offset() method to calculate the real X and Y coordinates of the <canvas>s top-left corner. I calculate the coordinates of the bottom-right corner by adding the values of outerWidth() and .outerHeight().

Basic demo: http://jsfiddle.net/75qbX/2/

var $canvas = $('canvas'), /* jQuery reference to the <canvas> */
    $canvasWidth = $canvas.outerWidth(), /* assuming height and width to be constant */
    $canvasHeight = $canvas.outerHeight();
function isCanvasClicked(x, y, target) {
    if (target.tagName === 'CANVAS') return true;
    var offset = $canvas.offset(),
        left = offset.left,
        top = offset.top;

    return x >= left && x <= left + $canvasWidth &&
           y >= top && y <= top + $canvasHeight;              
}
$('#mydiv').on('mousedown', '*', this, function(ev) {
    if (isCanvasClicked(ev.pageX, ev.pageY, ev.target)) {
        $canvas.fadeOut().fadeIn();
    }
});
Community
  • 1
  • 1
Rob W
  • 341,306
  • 83
  • 791
  • 678
  • This works very well, although your jsfiddle link had no Javascript in it. I modified it with something similar to what you posted here: http://jsfiddle.net/75qbX/6/. Thanks for sharing! – seanhodges Feb 27 '12 at 10:19
1

This should be the simplest solution, as already proposed:

http://jsfiddle.net/CUJ68/4/

$('canvas').on('mousedown', function(ev) {
    console.log(ev.target);
});
$('ul').on('mousedown', function(ev){
    $('canvas').mousedown();
});

if you need the original eventdata:

$('canvas').bind('mousedown', function(ev, parentEV) {
    if(parentEV){
        console.log(parentEV);
        alert("Canvas INdirectly clicked!");
    }else{
        console.log(ev);
        alert("Canvas directly clicked!");
    }
});
$('ul').on('mousedown', function(ev){
    $('canvas').trigger('mousedown', ev);
});
Alex
  • 6,276
  • 2
  • 20
  • 26
  • This works assuming you don't need the event data. I had trouble forwarding the ev result through (it caused an infinite loop in the event handler), or constructing my own Event (I could never get it to trigger the canvas mousedown callback). – seanhodges Feb 27 '12 at 10:23
  • http://jsfiddle.net/CUJ68/5/ This works for me. It passes `ev` to the `canvas`-event. – Alex Feb 27 '12 at 14:25
  • Excellent, the parent Event was the missing piece of the puzzle. Thanks Alex this is the tidy solution I was looking for. – seanhodges Feb 27 '12 at 21:01
0

Here you have a solution that consists in capture click event on above element, and triggering the event on the other: registering clicks on an element that is under another element

Community
  • 1
  • 1
mariogl
  • 1,195
  • 1
  • 10
  • 24