6

Is it possible to determine if an html element is visible to the user?

Example

A page has an input field with a datepicker. If the user clicks on the input field, another div appears which allows the user to select the desired date.

As long the datepicker is visible it hides elements which are behind it. I need a way to tell if an element is hidden or not.

First Approach

One way would be to check and compare the z-index values. But if they are note explicitly set, they are always auto.

Another way could be a way to check if an element is visible to the user. But i can't think of any way to do so.

The :visible selector does not work in this situation, because the element is only hidden to the user's eyes but still visible.

Any suggestions?

2
  • 1
    I doubt there's any way for JavaScript to detect if an overlapping image has transparent 'colors'. So even if you did identify that the object was completely covered by another image, you couldn't know if that image was actually obscuring what's beneath it. Commented Mar 28, 2012 at 19:57
  • You are right on that. But i can live with that restriction.
    – Alp
    Commented Mar 28, 2012 at 20:09

4 Answers 4

10

I tried a different approach using elements coordinates (getBoundingClientRect) and then using elementFromPoint to see if the element is hidden or visible.

DEMO (Follow the instruction on the right side)

        var rectPos = this.getBoundingClientRect();

        var result = 0;
        if (this == document.elementFromPoint(rectPos.left, 
                                                    rectPos.top)) {
            result++;
        }
        if (this == document.elementFromPoint(rectPos.left, 
                                                    rectPos.bottom - 1)) {
            result++;
        }
        if (this == document.elementFromPoint(rectPos.right - 1, 
                                                     rectPos.top)) {
            result++;
        }
        if (this == document.elementFromPoint(rectPos.right - 1, rectPos.bottom - 1)) {
            result++;
        }

        if (result == 4) {
            result = 'visible';
        } else if (result == 0) {
            result = 'hidden';
        } else {
            result = 'partially visible';
        }

Further Readings: getBoundingClientRect, elementFromPoint

2

This might work. I haven't tested it. It's a modified version of some code I found here.

function elementWithinElement(elemPossiblyCovered, elemPossiblyCovering)
{
    var top = elemPossiblyCovered.offsetTop;
    var left = elemPossiblyCovered.offsetLeft;
    var width = elemPossiblyCovered.offsetWidth;
    var height = elemPossiblyCovered.offsetHeight;

    while (elemPossiblyCovered.offsetParent)
    {
        elemPossiblyCovered = elemPossiblyCovered.offsetParent;
        top += elemPossiblyCovered.offsetTop;
        left += elemPossiblyCovered.offsetLeft;
    }

    return (
    top >= elemPossiblyCovering.offsetTop &&
    left >= elemPossiblyCovering.offsetLeft &&
    (top + height) <= (elemPossiblyCovering.offsetTop + elemPossiblyCovering.offsetHeight) &&
    (left + width) <= (elemPossiblyCovering.offsetLeft + elemPossiblyCovering.offsetWidth)
  );
}

So it'd be something like:

if(elementWithinElement(myTextbox, theDatepickerDiv))
{ 
    // It's hidden
}else
{
    //It's visible
}

Edit: Some of the code wasn't updated. Should be fixed now.

Edit Again: Fixed the code and tested it. It works!

5
  • Assumed i dont know which one of both elements is in the front, how can i determine it?
    – Alp
    Commented Mar 28, 2012 at 20:07
  • If you needed that, I suppose you could do elementWithinElement(first, second) If true, "second" is in front. Then, elementWithinElement(second, first) If true, "first" is in front.
    – IanW
    Commented Mar 28, 2012 at 20:12
  • I tried it on this page with elementWithinElement(jQuery('#chat-feature'), jQuery('#sidebar')) but it returns false.
    – Alp
    Commented Mar 28, 2012 at 20:12
  • Oh, try grabbing the javascript object. elementWithinElement(jQuery('#chat-feature').get(0), jQuery('#sidebar').get(0)) Also note that I had to edit the function a couple times when I realized some mistakes, so make sure you have the current version :)
    – IanW
    Commented Mar 28, 2012 at 20:14
  • oh sure, you are right. thanks, that works. let me test it on my page.
    – Alp
    Commented Mar 28, 2012 at 20:16
1

the only way I can think of is by getting the offset of each item and checking that onclick of something that the offset of the new item isn't within the offset of anything previous. Obviously that just the theory behind it making something that does that will take a long time. Good luck :)

5
  • Good answer. I think this might be the only way. Get the rectangle coords of the element you're checking and see if that's within the rectangle of the datepicker div. To get the rectangle of the object, get the X and Y offset (top left point of the rectangle) and then x + width, y + height (bottom right point of the rectangle)
    – IanW
    Commented Mar 28, 2012 at 19:49
  • Yeah exactly what I was thinking. Sounds easy in theory putting into practice I can imagine is a little harder. Although you might be pleasantly surprised :) Commented Mar 28, 2012 at 19:51
  • What you describe is something like collision detection, which should be quite straightforward to implement. But how can i determine which of both is in the front?
    – Alp
    Commented Mar 28, 2012 at 20:04
  • @Alp Check out my answer with the code. Are you using a jQuery DatePicker? If so, the datepicker element will be 'display:none' when it's hidden. In this case, elementWithinElement would return false (meaning the element underneath is visible)
    – IanW
    Commented Mar 28, 2012 at 20:10
  • I guess you'd have to add z-indexing to the item you want in the front. and then check that the z-index is higher than 0 Commented Mar 28, 2012 at 20:12
0
function traversParents(el, callback, depth = 10) {
  let level = depth;
  let parent = el.parentElement;
  while (level > 0 && parent) {
    const stop = callback(parent);
    if (stop) break;
    parent = parent.parentElement;
    level--;
  }
}


function findTopmost(els) {
    let highest = 0;
    let highestZIndex = 0;

    const getZIndex = (el) => {
      const computedStyle = window.getComputedStyle(el);
      const isAbsolute = computedStyle.getPropertyValue('position') === 'absolute' ? 0.5 : 0;
      return Number.parseInt(computedStyle.getPropertyValue('z-index')) || isAbsolute;
    }

    els.forEach((el, i) => {
      let zIndex = getZIndex(el);
      traversParents(el, (parent) => {
        if (zIndex > 0) return true;
        zIndex = getZIndex(parent);
      }, 15);
      if (zIndex > highestZIndex) {
        highestZIndex = zIndex;
        highest = i;
      }
    });

    return els[highest];
 }

Not the answer you're looking for? Browse other questions tagged or ask your own question.