JavaScript tip: How to find the document elements that intersect at a certain coordinate

doug
Sun, 12 Apr 2009 12:18:19 +0000

This is going to be mostly a code post.

First thing we need is a method of getting the coordinate of elements in the document. Here is a function I created to do that.

/**
 * Get the offset coordinates of the element.
 * @param {Object} element The element.
 * @return {Object} An object with four properties named x1, x2, y1 and y2 containing the elements left, right, top and bottom edges respectively.
 */
function getElementPosition(element) {
    var elementX = 0;
    var elementY = 0;
    var elementW = element.offsetWidth;
    var elementH = element.offsetHeight;

    while (element.offsetParent) {
        elementX += element.offsetLeft;
        elementY += element.offsetTop;
        element = element.offsetParent;
    }

    elementX += element.offsetLeft;
    elementY += element.offsetTop;
    elementW += elementX;
    elementH += elementY;

    return {x1: elementX, y1: elementY, x2: elementW, y2: elementH};
}

The next thing we need is a a function to calculate the intersection that makes use of the above function.

/**
 * Finds all elements that intersects with the given coordinates.
 * @param {Object} doc The document DOM object.
 * @param {Integer} x The X coordinate of the doc.
 * @param {Integer} y The Y coordinate of the doc.
 * @param {String} [tagName] An optional tag name filter.
 * @param {Function} [cmpCallback] A call back function to test whether or not an element should be included in the results (such as testing element id's, values, etc). The call back should return true or false.
 * @return {Array} Returns a primitive array of elements that intersects with point (x, y).
 */
function getElementsIntersectingPoint(doc, x, y, tagName, cmpCallback) {
    var elements = [];
    var results = [];
    var i = 0;
    var element = null;
    var pt = null;

    if (tagName == undefined || tagName == null) {
        tagName = "*";
    }
    if (cmpCallback == undefined || cmpCallback == null) {
        cmpCallback = function(e) { return true };
    }

    elements = doc.getElementsByTagName(tagName);

    for(i = 0; i < elements.length; i++) {

        element = elements[i];

        if (cmpCallback(element)) {

            pt = getElementPosition(element);

            if (x >= pt.x1 && x <= pt.x2 && y >= pt.y1 && y <= pt.y2) {
                results[results.length] = element;
                continue;
            }


        }
    }

    return results;
}

So there we have it! But wait, we can do more! Now let's introduce a function that finds the top most element that intersects with a given coordinate.

/**
 * Finds the top most (the element with the highest z-Index) element that intersects with the given coordinates.
 * @param {Object} doc The document DOM object.
 * @param {Integer} x The X coordinate of the doc.
 * @param {Integer} y The Y coordinate of the doc.
 * @param {String} [tagName] An optional tag name filter.
 * @param {Function} [cmpCallback] A call back function to test whether or not an element should be included in the results (such as testing element id's, values, etc). The call back should return true or false.
 * @return {Object} Returns the element with the highest z-Index that intersects with point (x, y).
 */

function getTopElementIntersectingPoint(doc, x, y, tagName, cmpCallback) {
    var elements = getElementsIntersectingPoint(doc, x, y, tagName, cmpCallback);
    var topZIndex;
    var topElement = null;
    var i = 0;
    for(i = 0; i < elements.length; i++) {
        zIndex = parseInt(elements[i].style.zIndex);
        if (topZIndex == undefined || zIndex >= topZIndex) {
            topZIndex = zIndex;
            topElement = elements[i];
        }
    }
    return topElement;

}

Tada! If you'd like to test this functionality then here is some code to generate a test document for you. Name the file containing the above functions "pointintersection.js" then create a new HTML file perhaps named "pointintersection.htm" and paste this code:






function createMyDocument() {
    var div;
    div = document.createElement("div");
    div.style.position = "absolute";
    div.style.zIndex = 3;
    div.style.left = 100;
    div.style.top = 200;
    div.style.width = 100;
    div.style.height = 100;
    div.id = "block1";
    div.className = "block1";
    div.appendChild(document.createTextNode("Block 1"));
    document.body.appendChild(div);


    div = document.createElement("div");
    div.style.position = "absolute";
    div.style.zIndex = 2;
    div.style.left = 150;
    div.style.top = 250;
    div.style.width = 100;
    div.style.height = 100;
    div.id = "block2";
    div.className = "block2";
    div.appendChild(document.createTextNode("Block 2"));
    document.body.appendChild(div);


    div = document.createElement("div");
    div.style.position = "absolute";
    div.style.zIndex = 4;
    div.style.left = 200;
    div.style.top = 300;
    div.style.width = 100;
    div.style.height = 100;
    div.id = "block3";
    div.className = "block3";
    div.appendChild(document.createTextNode("Block 3"));
    document.body.appendChild(div);

    document.onmousemove = intersection;
}

function intersection(event) {
    var intersected = getElementsIntersectingPoint(document, event.clientX, event.clientY);
    var topmost = getTopElementIntersectingPoint(document, event.clientX, event.clientY);
    var output = document.getElementById("output");
    var topid;
    if (!topmost) {
        topid = "undefined";
    } else {
        topid = "'" + topmost.id + "'";
    }
var summary = " Mouse is at (" + event.clientX + "," + event.clientY + ") " +
" The top most element is: " + topid + " " +
" The intersected elements are: [";
    var i = 0;
    for(i = 0; i < intersected.length; i++) {
        if (i > 0) {
            summary += ", ";
        }
        summary += "'" + intersected[i].id + "'";
    }
summary += "] ";
    output.innerHTML = summary;
}


.block1 {
    background-color: blue;
    border: 1px solid black;
}
.block2 {
    background-color: red;
    border: 1px solid black;
}
.block3 {
    background-color: green;
    border: 1px solid black;
}