12

You can use this to find a document element at a given point

document.elementFromPoint(x, y);

What can you do if there are overlapping elements at the point? (I know this is not a great way to do things -- trying a hackish workaround for a bug before a deadline).

bernie2436
  • 21,121
  • 44
  • 141
  • 234

5 Answers5

24

As I think you already know, document.elementFromPoint(x, y); only returns the top-most element that overlaps that point.

If what you're trying to do is find all elements that overlap with a given point, even elements behind other elements, then I'm not aware of any DOM function that will do that for you. You may have to write your own.

A somewhat hackish version would be to call elementFromPoint(x,y), remember that DOM item, then hide that item with display: none, then call elementFromPoint(x,y) again until all you get is the body, then restore the items you hid.

A less hackish version would be to cycle though all objects in the page and compare their offset/height/width in the page to your point.

Here's one way to do it:

function getAllElementsFromPoint(x, y) {
    var elements = [];
    var display = [];
    var item = document.elementFromPoint(x, y);
    while (item && item !== document.body && item !== window && item !== document && item !== document.documentElement) {
        elements.push(item);
        display.push(item.style.display);
        item.style.display = "none";
        item = document.elementFromPoint(x, y);
    }
    // restore display property
    for (var i = 0; i < elements.length; i++) {
        elements[i].style.display = display[i];
    }
    return elements;
}

Working demo: http://jsfiddle.net/jfriend00/N9pu9/

jfriend00
  • 637,040
  • 88
  • 896
  • 906
  • thanks a lot. super helpful in resolving a nasty bug – bernie2436 Mar 15 '14 at 20:17
  • 5
    A better hack to this could be this - https://gist.github.com/Rooster212/4549f9ab0acb2fc72fe3. It uses pointer-events rather than display none. – Ashish Singh Mar 08 '16 at 05:05
  • 3
    Thanks everyone, and, btw - as of August 2018 most browsers now supports elementsFromPoint method (note, this is element**s**) - https://developer.mozilla.org/en-US/docs/Web/API/DocumentOrShadowRoot/elementsFromPoint - which already is addressed in the above gist code by ashish as well. – konrados Aug 21 '18 at 09:11
  • 1
    i didn't even know that `elementFromPoint` kind of thing exits ! thanks – Dhaval Pankhaniya Nov 23 '18 at 14:41
24

If you want to find all the DOM elements that overlap over a point, you can simply use document.elementsFromPoint(), which returns an array of all elements found in said point (apparently, ordered from top to bottom relative to the viewport, i.e.: if there's an overlay, it will show up first in the array)

Sebastianb
  • 1,930
  • 22
  • 29
5

Not sure for what reason the initial question was asked, but if you need to detect an element under currently draggable with touch or mouse element, you can set pointer-events: none; on the draggable element. When you do this, document.elementFromPoint() will ignore currently draggable element and return the one underneath it.

zmechanic
  • 1,686
  • 18
  • 26
  • 1
    Thanks, I've been struggling with getting ```document.elementFromPoint()``` to behave when dragging and this resolved the issue. – nevf Nov 13 '18 at 06:22
2

So from what I can see, this answer - https://stackoverflow.com/a/54350762/479836 - would be the ideal, but unfortunately, document.elementsFromPoint() does not work in IE11.

Failing that, the code in this answer - https://stackoverflow.com/a/22428553/479836 appears to work across most modern browsers, including IE11, but there's a bug where certain DOM elements are duplicated in the array returned by the function.

However, if anyone's looking for an document.elementsFromPoint() shim, a good one can be found here:

https://gist.github.com/oslego/7265412

Copied here for convenience:

function elementsFromPoint(x,y) {
    var elements = [], previousPointerEvents = [], current, i, d;

        // get all elements via elementFromPoint, and remove them from hit-testing in order
    while ((current = document.elementFromPoint(x,y)) && elements.indexOf(current)===-1 && current != null) {

            // push the element and its current style
        elements.push(current);
        previousPointerEvents.push({
                value: current.style.getPropertyValue('pointer-events'),
                priority: current.style.getPropertyPriority('pointer-events')
            });

            // add "pointer-events: none", to get to the underlying element
        current.style.setProperty('pointer-events', 'none', 'important'); 
    }

        // restore the previous pointer-events values
    for(i = previousPointerEvents.length; d=previousPointerEvents[--i]; ) {
        elements[i].style.setProperty('pointer-events', d.value?d.value:'', d.priority); 
    }

        // return our results
    return elements;
}
Charlie
  • 3,993
  • 3
  • 39
  • 57
0

Simple Solution

In case $e is the overlapping element, e is the event

$e.style.display = 'none';
var b = document.elementFromPoint(e.clientX,e.clientY);
$e.style.display = 'block';