Search code examples
javascriptjqueryhtmldommousehover

Programatically detect whether an element is able to be be hovered or not


Using a bookmarklet I would like to bind an img element with an handler for mouseenter and mouseleave events using jquery .hover() but in some cases the mouse events will never be triggered because the img element is somehow hidden from the mouse despite the image is in fact visible : a simple example is when the is overlapped by a transparent element. If you play with a tool like the firebug inspector you will find many examples like this.

How can I figure out this kind of behavior ? Ideally I would like to build a function that will return a true or false depending if the element is hoverable or not.

Thanks to all


Solution

  • The problem:

    The real problem is that there are so many ways that an element can be covered. For example, is the element covered with 4 other elements and just a space is open in the middle? Example of what I mean:

    enter image description here

    Assuming the red box is the element in question, the small box directly in the center is a direct view the element.

    Because of these problems, it can get very "expensive" to determine if the element has any hoverable area.

    The solution:

    I came up with a solution using boundingClientRect, document.elementFromPoint and document scrolling. It tries to determine fast, by checking the 4 corners of the element in question, and then if those come back false, to begin checking the entire element by a determined "accuracy".

    UPDATE:

    I have added in a way to check elements that are not even in the viewport. This works by attempting to scroll the the element, running the check, and then scrolling back to the previous position before the check began. This happens so fast that no visible jumps occur.

    function getOffset(el) {
        el = el.getBoundingClientRect();
        return {
            left: el.left + window.scrollX,
            top: el.top + window.scrollY
        }
    }
    
    function isElementHoverable(element) {
        var pageX = window.scrollX;
        var pageY = window.scrollY;
        var elementXY = getOffset(element);
        var accuracy = 1;
        var canBeHovered = false;
    
        window.scrollTo(elementXY.left, elementXY.top);
    
        var box = element.getBoundingClientRect();
    
        // try the 4 corners first
        if (
            element == document.elementFromPoint(box.left, box.top) ||
            element == document.elementFromPoint(box.left, box.bottom - 1) ||
            element == document.elementFromPoint(box.right - 1, box.top) ||
            element == document.elementFromPoint(box.right - 1, box.bottom - 1)
        ) {
            canBeHovered = true;
        }
    
        loop1:
            for (let i = box.left; i < box.right; i += accuracy) {
                for (let j = box.top; j < box.bottom; j += accuracy) {
                    if (element == document.elementFromPoint(i, j)) {
                        canBeHovered = true;
                        break loop1;
                    }
                }
            }
    
        window.scrollTo(pageX, pageY); // Scroll back to the original position
        return canBeHovered;
    }
    

    I worked up an actual example on JSFiddle (example has been updated to reflect elements scrolled down the page), complete with a couple of tests. You will need to open the browser console to see the results of the tests. You will also probably want to test this in Chrome or Firefox first as I didn't test this outside of that.


    A few things to remember:

    If the 4 corner check fails, then it gets expensive to check the rest of the element. The smaller the "accuracy" number, the more expensive it gets. That number determines how often to run the check. So if the accuracy is 1, then it will check every 1px. If the number is 10, then it will check every 10px on the X and Y axis.

    It should be noted that there is almost no way to know the variations of elements in the page. You mention in a comment that some elements may disappear that were "blocking" during the time of the check, but are not while a user is interacting. There is no way to know of this functionality short of AI. However, this function is meant to be called upon your choosing, not just on page load.