How to propagate a click to all divs under cursor?

2020-08-17 07:22发布

问题:

I have a bunch of divs postioned absolutely on top of each other. When I bind a click event to all of them, only the top div responds. How can I send the event to all divs under the cursor?

回答1:

Taking FelixKling's suggestion to use document.elementFromPoint() and Amberlamps's fiddle, and employing jQuery for the DOM interactions, I ended up with the following :

$divs = $("div").on('click.passThrough', function (e, ee) {
  var $el = $(this).hide();
  try {
    console.log($el.text());//or console.log(...) or whatever
    ee = ee || {
      pageX: e.pageX,
      pageY: e.pageY
    };
    var next = document.elementFromPoint(ee.pageX, ee.pageY);
    next = (next.nodeType == 3) ? next.parentNode : next //Opera
    $(next).trigger('click.passThrough', ee);
  } catch (err) {
      console.log("click.passThrough failed: " + err.message);
  } finally {
    $el.show();
  }
});

DEMO

try/catch/finally is used to ensure elements are shown again, even if an error occurs.

Two mechanisms allow the click event to be passed through or not :

  • attaching the handler to only selected elements (standard jQuery).
  • namespacing the click event, click.passThrough analogous to event.stopPropagation().

Separately or in combination, these mechanisms offer some flexibility in controlling the attachment and propagation of "passThrough" behaviour. For example, in the DEMO, try removing class p from the "b" element and see how the propagation behaviour has changed.

As it stands, the code needs to be edited to get different application-level behaviour. A more generalized solution would :

  • allow for programmatic attachment of app-specific behaviour
  • allow for programmatic inhibition of "passThrough" propagation, analogous to event.stopPropagation().

Both of these ambitions might be achieved by establishing a clickPassthrough event in jQuery, with underlying "passThrough" behaviour, but more work would be involved to achieve that. Maybe someone would like to have a go.



回答2:

This is not as easy as you might think. This is a solution that I came up with. I only tested it in Chrome and I did not use any framework.

The following snippet is just for add a click event to every div in the document, that outputs its class name when triggered.

var divs = document.getElementsByTagName("div");
for(var i = 0; i < divs.length; i++) {
    divs[i].onclick = function() {
        console.log("class clicked: " + this.className);
    };
}

Attaching a click event to the body element so that every single click event is noticed by our script.

if(document.addEventListener) {
    document.body.addEventListener("click", countDivs);
} else if(document.attachEvent) {
    document.attachEvent("onclick", countDivs);
}

Iterate through all divs that you want to check (you might want to adjust here to your preferred range of divs). Generate their computed style and check whether the mouse coordinates are within the range of the div´s position plus its width and height. Do not trigger click event when the div is our source element because the click event has already been fired by then.

function countDivs(e) {
    e = e || window.event;
    for(var i = 0; i < divs.length; i++) {
        var cStyle = window.getComputedStyle(divs[i]);
        if(divs[i] !== e.target && e.pageX >= parseInt(cStyle.left) && e.pageX <= (parseInt(cStyle.left) + parseInt(cStyle.width)) && e.pageY >= parseInt(cStyle.top) && e.pageY <= (parseInt(cStyle.top) + parseInt(cStyle.height))) {
            divs[i].click();
        }
    }
}

CSS:

.a, .b, .c {
  position: absolute;
  height: 100px;
  width: 100px;
  border: 1px #000 solid
}
.a {
  top: 100px;
  left: 100px;
}
.b {
  top: 120px;
  left: 120px;
}
.c {
  top: 140px;
  left: 140px;
}

HTML:

<div class="a"></div>
<div class="b"></div>
<div class="c"></div>

I also added a jsFiddle



回答3:

A simple way could be to use elementFromPoint():

http://jsfiddle.net/SpUeN/1/

var clicks = 0,cursorPosition={};

$('div').click(function (e) {
    if(typeof cursorPosition.X === 'undefined') {
         cursorPosition.X = e.pageX;
        cursorPosition.Y = e.pageY;
    }
    clicks++;
    e.stopPropagation();
    $(this).addClass('hided');
    var underELEM = document.elementFromPoint(cursorPosition.X, cursorPosition.Y);
    if (underELEM.nodeName.toUpperCase() === "DIV") $(underELEM).click();
    else {
        $('#clicks').html("Clicks: " + clicks);
        $('.hided').removeClass('hided');
        clicks=0;
        cursorPosition = {};
    }
});


回答4:

If you are stacking elements absolutely it may be simpler to stack them all in a positioned container, and handle the events from this parent. You can then manipulate its children without having to measure anything.