registering clicks on an element that is under another element

19,835

Solution 1

OK, so there are 2 things I think I would do: (1) a CSS3 answer, that isn't yet widely adopted, and (2) a Javascript backup-plan for the rest. (not sure what to do about users without JS on older browsers that don't support the CSS part). Read on...

First: Use this CSS3 code to make the upper element 'invisible' to mouse-interaction:

pointer-events: none;

This is not yet cross-browser compatible. It only works on SVG & HTML Elements in Gecko & Webkit browsers, but only on SVG elements in Opera, and not at all in IE.

Per the MDN:

The CSS property pointer-events allows authors to control whether or when an element may be the target of a mouse event. This property is used to specify under which circumstance (if any) a mouse event should go "through" an element and target whatever is "underneath" that element instead.

Here's that documentation: https://developer.mozilla.org/en/css/pointer-events

Second: Use some javascript Feature Detection for those browsers that don't yet support this CSS. You'll have to roll your own, similar to Modernizr, since it doesn't test for this yet (to my knowledge). The idea is that you create an element, apply the pointer-events CSS to it, check for it, and if the browser supports the feature, you'll get the expected result, as opposed to something null or undefined; then destroy the element. Anyway, once you have feature detection, you can use jQuery or similar to attach a listener to the uppermost element and assign a click from it to click through to something else. Check out jQuery and it's use of the click() function for more info (not enough reputation to post the link, sry).

If I get time later, I might be able to work up a quick jsFiddle on it. I hope this helps.

Solution 2

If you topmost element is contained within the element below, you can let the event "bubble through". If it is not, you have to trigger the click manually. There is no way to fire the event handler just by clicking.

To fire the event manually, you could do this:

$("#topelement").click(function() {
   $("#elementbelow").click();
   return false;
});

EDIT (response to comments):

To enumerate all boxes, you can do this (off the top of my head. no testing, could not even be syntax-error free)

$("#topelement").click(function(e) {
    $(".box").each(function() {
       // check if clicked point (taken from event) is inside element
       var mouseX = e.pageX;
       var mouseY = e.pageY;
       var offset = $(this).offset;
       var width = $(this).width();
       var height = $(this).height();

       if (mouseX > offset.left && mouseX < offset.left+width 
           && mouseY > offset.top && mouseY < offset.top+height)
         $(this).click(); // force click event
    });
});

Solution 3

I have modified the code from above into jquery function, somebody might find it useful:

Find alternative clicked-on elements:

$('.child').click(function(e) {
    $(this).siblingsUnderMouse(e).each(fn);
});

If you want to ignore transparent pixels in images:

$('.child').click(function(e) {
    $(this).siblingsUnderMouse(e).each(function() {
        var clickOnVisiblePixel = $(this).isOnVisiblePixel(e);
    });
});

Code:

$.fn.reverse = [].reverse;
$.fn.siblingsUnderMouse = function(e, filter)
{
    filter = filter || '*';

    var arr = $();
    this.parent().children().filter(filter).reverse().each(function()
    {
        var t = $(this);
        // check if clicked point (taken from event) is inside element
        var x = (e.pageX - t.offset().left).toFixed();
        var y = (e.pageY - t.offset().top).toFixed();
        var w = t.width();
        var h = t.height();

        if (x < 0 || x >= w || y < 0 || y >= h)
            return;

        arr.push(t);
    });
    return arr;
}

$.fn.isOnVisiblePixel = function(e)
{
    if (e.type == 'mouseleave') return false;

    var t = this[0];
    if (!t || t.tagName != 'IMG')
        t = this.find('img')[0];
    if (!t) return;

    var w = $(t).width();
    var h = $(t).height();
    var o = $(t).offset();
    var x = (e.pageX - o.left).toFixed();
    var y = (e.pageY - o.top).toFixed();
    if (x < 0 || y < 0 || x >= w || y >= h)
        return false;

    var c = $('<canvas>')[0];
    c.width = w;
    c.height = h;

    var ctx = c.getContext('2d');
    ctx.drawImage(t, 0, 0, w, h);

    var a = ctx.getImageData(x, y, 1, 1).data[3];
    return (a > 128);
}
Share:
19,835

Related videos on Youtube

Smilediver
Author by

Smilediver

UI Developer / Front End Developer My CV on careers.stackoverflow.com

Updated on August 31, 2020

Comments

  • Smilediver
    Smilediver over 3 years

    I have elements that are under an element with opacity:0.5 that I want to be able to click on. How can I click "through" the topmost element?

    Here's an example that demonstrates my problem. Click on the boxes to toggle them on and off. You can edit it on jsbin to try out your solution.

    Bonus points if you can have the boxes toggle on hover.

    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 
      "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> 
    <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> 
    <head> 
    <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.3.2/jquery.min.js"></script> 
    <title>Sandbox</title> 
    <meta http-equiv="Content-type" content="text/html; charset=utf-8" /> 
    <style type="text/css" media="screen"> 
    body { background-color: #000; } 
    .box {width: 50px; height: 50px; border: 1px solid white} 
    .highlight {background-color: yellow;} 
    </style>
    <script type="text/javascript">
    var dthen = new Date();
    $('<div id="past">').css({'height':  (dthen.getMinutes()*60)+dthen.getSeconds() +'px'
                ,'position': 'absolute'
                ,'width': '200px'
                ,'top': '0px'
                ,'background-color': 'grey'
                ,'opacity': '0.5'
                })
            .appendTo("#container");
    
    setInterval(function(){
        dNow = new Date();
        $('#past').css('height', ((dNow.getSeconds()+(dNow.getMilliseconds()/1000))*50)%300 +'px');
    },10)
    
     $(".box").click(function(){
          $(this).toggleClass("highlight");
        });
    </script>
    </head> 
    <body> 
      <div id="container"> 
         <div class="box" style="position:absolute; top: 25px; left: 25px;"></div> 
         <div class="box" style="position:absolute; top: 50px; left: 125px;"></div> 
         <div class="box" style="position:absolute; top: 100px; left: 25px;"></div> 
         <div class="box" style="position:absolute; top: 125px; left: 125px;"></div> 
         <div class="box" style="position:absolute; top: 225px; left: 25px;"></div> 
         <div class="box" style="position:absolute; top: 185px; left: 125px;"></div> 
      </div> 
    </body> 
    </html>
    
    • Philippe Leybaert
      Philippe Leybaert almost 15 years
      I don't think we're here to write out a complete solution for you. Using the suggestions given below, you should implement the solution yourself. SO is not a free consultancy shop.
    • Smilediver
      Smilediver almost 15 years
      I can't believe I'm the only person who will ever want to do this. So I think any code that demonstrates how to do it would be find use beyond what I have planned for it. I don't see any problem with asking for code to do this.
    • redsquare
      redsquare almost 15 years
      sam, also use addClass rather than all those settings using .css
  • Philippe Leybaert
    Philippe Leybaert almost 15 years
    You would have to enumerate all DOM elements to see if the clicked point is inside one or more elements. There is no way the browser can do this for you
  • Smilediver
    Smilediver over 6 years
    This works! Compare the point-events solution with the original solution. Of course pointer-events didn't exist when I first asked this in 2009. IE11 was the first version to support pointer-events and that came out in 2013.
  • Smilediver
    Smilediver over 6 years
    If you need to support IE10 or lower, Modernizr now appears to have a test for CSS Pointer Events
  • Smilediver
    Smilediver over 6 years
    This also solves the hover issue. You can just attach styles to .box:hover and it will still trigger if under the #past element.