Determine visibility / real z-index of html elements
Asked Answered
B

4

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?

Briar answered 28/3, 2012 at 19:35 Comment(2)
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.Archaeology
You are right on that. But i can live with that restriction.Briar
D
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!

Depone answered 28/3, 2012 at 19:56 Comment(5)
Assumed i dont know which one of both elements is in the front, how can i determine it?Briar
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.Depone
I tried it on this page with elementWithinElement(jQuery('#chat-feature'), jQuery('#sidebar')) but it returns false.Briar
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 :)Depone
oh sure, you are right. thanks, that works. let me test it on my page.Briar
C
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

Cue answered 28/3, 2012 at 20:58 Comment(0)
D
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!

Depone answered 28/3, 2012 at 19:56 Comment(5)
Assumed i dont know which one of both elements is in the front, how can i determine it?Briar
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.Depone
I tried it on this page with elementWithinElement(jQuery('#chat-feature'), jQuery('#sidebar')) but it returns false.Briar
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 :)Depone
oh sure, you are right. thanks, that works. let me test it on my page.Briar
B
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 :)

Baudin answered 28/3, 2012 at 19:39 Comment(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)Depone
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 :)Baudin
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?Briar
@Briar 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)Depone
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 0Baudin
H
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];
 }
Himalayas answered 24/5, 2024 at 7:52 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.