132

In JavaScript, how would you check if an element is actually visible?

I don't just mean checking the visibility and display attributes. I mean, checking that the element is not

  • visibility: hidden or display: none
  • underneath another element
  • scrolled off the edge of the screen

For technical reasons I can't include any scripts. I can however use Prototype as it is on the page already.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Macha
  • 14,366
  • 14
  • 57
  • 69
  • Can you elaborate on the technical issues that disable you from including libraries? I have read the same problem in several cases, but can't think of a scenario it is relevant (in XHTML documents i.e) – Itay Moav -Malimovka May 23 '09 at 13:18
  • The second requirement raises new problems: what about those elements that only partially cover others? Or cover fully but, for instance, have background image that is transparent so you can see the underneath elements. Should we count those underneath elements as visible or not? – viam0Zah May 23 '09 at 13:55
  • @Itay The code is running inside a Selenium test. @Török For simplicity (i.e. It doesn't happen in this case), you can call both of them as not visible. – Macha May 24 '09 at 13:41
  • What about new HTML5 `hidden` attribute? (it is already supported by modern browsers). `` – Konstantin Smolyanin Aug 27 '13 at 20:37
  • I suggest a solution regarding the `overflow:hidden` situation here: http://stackoverflow.com/questions/31588220/how-to-test-if-an-element-inside-a-carousel-a-container-with-overflowhidden – Yuval A. Aug 12 '15 at 07:36
  • I always feel bad voting to close an older question as a duplicate of a newer one. Unfortunately nobody closed the newer one at the time, and that has since gone on to attain slightly more views and a more popular answer. – James Donnelly Nov 09 '16 at 23:30
  • I'd hate to redirect you to jQuery (like often is done), [but this discussion](http://remysharp.com/2008/10/17/jquery-really-visible/) about when elements are really visible is very insightful. And since [jQuery 1.3.2](http://code.google.com/p/jqueryjs/downloads/detail?name=jquery-1.3.2.min.js&downloadBtn=) this is [no longer a problem](http://remysharp.com/2008/10/17/jquery-really-visible/#comment-135222). – Ólafur Waage Apr 01 '09 at 09:33
  • That solves the first part and the third part but what about the second? How to tell if it's beneath another element. Also, for technical reasons, I can't use jQuery, or any other includes, although Prototype is available already. – Macha Apr 01 '09 at 09:36
  • The accepted answer on the "duplicate" question relies (merely) on the display attribute, which this question discards as a *partial* solution. Additionally, there is a suitable answer here (the accepted one). – jpaugh Dec 01 '20 at 10:37
  • @JamesDonnelly Consider reopening the question. The newer question's accepted answer will not solve this question's problem. They are different questions. (See my also previous comment.) Thanks! – jpaugh Dec 01 '20 at 10:43
  • The newer one is not the same as this one - this one also asks about checking for occluded element. – Macha May 19 '22 at 19:46

15 Answers15

106

For the point 2.

I see that no one has suggested to use document.elementFromPoint(x,y), to me it is the fastest way to test if an element is nested or hidden by another. You can pass the offsets of the targetted element to the function.

Here's PPK test page on elementFromPoint.

From MDN's documentation:

The elementFromPoint() method—available on both the Document and ShadowRoot objects—returns the topmost Element at the specified coordinates (relative to the viewport).

jpaugh
  • 6,634
  • 4
  • 38
  • 90
Christophe Eblé
  • 8,071
  • 3
  • 33
  • 32
  • Isn't that a IE only solution ? – Bite code Oct 05 '09 at 16:08
  • @e-satis: It works in Firefox for me. It doesn't work in Opera. – Macha Oct 05 '09 at 18:26
  • 6
    What about transparency of elements? I suppose you may get the situation when `elementFromPoint()` says that element is completely overlapped by another (and you treat it as invisible) but user can see it. – Konstantin Smolyanin Aug 27 '13 at 20:02
  • @KonstantinSmolyanin You're asking about something different than the OP, to see if there are elements under a certain element (not if elements are above a certain element). For that, you can always change the topmost element's CSS to have `display: none` and check the same area again. If something else shows up that's not a parent element of the previously topmost element, then something was underneath. – Pluto Sep 03 '15 at 01:04
  • 1
    This methods failed if we want to check the visibility of a parent element. So, at the specified point of the parent, the child would be available, while it is a part of parent. Just consider a parent `
    ` which includes several nested elements. In fact, parent `
    ` is valid and visible, but the output is not showing this.
    –  Dec 18 '15 at 15:59
  • This makes sense, yet does not work well with nested elements. Say, elements nested within an anchor tag. The elements within the anchor tag are returned, and not the anchor tag itself. – Questionnaire Feb 25 '20 at 11:23
42

I don't know how much of this is supported in older or not-so-modern browsers, but I'm using something like this (without the neeed for any libraries):

function visible(element) {
  if (element.offsetWidth === 0 || element.offsetHeight === 0) return false;
  var height = document.documentElement.clientHeight,
      rects = element.getClientRects(),
      on_top = function(r) {
        var x = (r.left + r.right)/2, y = (r.top + r.bottom)/2;
        return document.elementFromPoint(x, y) === element;
      };
  for (var i = 0, l = rects.length; i < l; i++) {
    var r = rects[i],
        in_viewport = r.top > 0 ? r.top <= height : (r.bottom > 0 && r.bottom <= height);
    if (in_viewport && on_top(r)) return true;
  }
  return false;
}

It checks that the element has an area > 0 and then it checks if any part of the element is within the viewport and that it is not hidden "under" another element (actually I only check on a single point in the center of the element, so it's not 100% assured -- but you could just modify the script to itterate over all the points of the element, if you really need to...).

Update

Modified on_top function that check every pixel:

on_top = function(r) {
  for (var x = Math.floor(r.left), x_max = Math.ceil(r.right); x <= x_max; x++)
  for (var y = Math.floor(r.top), y_max = Math.ceil(r.bottom); y <= y_max; y++) {
    if (document.elementFromPoint(x, y) === element) return true;
  }
  return false;
};

Don't know about the performance :)

onli
  • 183
  • 12
Tobias
  • 1,938
  • 16
  • 12
  • 1
    What is the element.offsetWidth/offsetHeight trying to find? They seem to always return 0 in Chrome. And the document.documentElement.clientHeight is getting the height of the element; shouldn't it be document.body.clientHeight? – Seanonymous Feb 13 '13 at 19:00
  • For visible height of the window, this seems to work cross-browsers (even old IEs): `height = window.innerHeight?window.innerHeight:document.documentElement.clientHeight;` – Seanonymous Feb 13 '13 at 19:22
  • 1
    What if an element is completely overlapped by another (in terms of this approach) but that overlapping element has some transparency? So the element below is visible to user but treated as not visible by the method. – Konstantin Smolyanin Aug 27 '13 at 19:57
  • Original on_top function is not consistent for me and the modified one lacks of performance too much. But the rest is pretty sexy :) – Florian Leitgeb Jun 16 '15 at 11:14
  • Modified on_top function worked wonders for me... Thank you @Tobias – Madhavan Sundararaj May 25 '20 at 05:37
7

As jkl pointed out, checking the element's visibility or display is not enough. You do have to check its ancestors. Selenium does this when it verifies visibility on an element.

Check out the method Selenium.prototype.isVisible in the selenium-api.js file.

http://svn.openqa.org/svn/selenium-on-rails/selenium-on-rails/selenium-core/scripts/selenium-api.js

Jason Tillery
  • 71
  • 1
  • 1
  • Thanks. The code is now moved to: https://code.google.com/p/selenium/source/browse/javascript/selenium-core/scripts/selenium-api.js?r=789dad2290856eddeffea32789c9b1339ea60954 – Sali Hoo Jan 15 '15 at 19:51
  • 1
    And the code has moved again: https://github.com/SeleniumHQ/selenium/blob/master/javascript/selenium-core/scripts/selenium-api.js#L1885 – Tobias Jun 20 '15 at 10:37
  • That's not true, Selenium checks for isVisible only by verifying css 'display' and 'visibility' properties, in the link you share https://github.com/SeleniumHQ/selenium/blob/master/javascript/selenium-core/scripts/selenium-api.js#L1885 Selenium.prototype.isVisible ... return (visibility != "hidden" && _isDisplayed); – Liraz Shay Dec 13 '16 at 11:30
  • But ChromeDriver throws ElementNotClickable at point exception is the element is actually hidden under another element and will not receive the click. but I think other browser does not throwing it (I'm sure that Firefox not checking) – Liraz Shay Dec 13 '16 at 11:36
4

Interesting question.

This would be my approach.

  1. At first check that element.style.visibility !== 'hidden' && element.style.display !== 'none'
  2. Then test with document.elementFromPoint(element.offsetLeft, element.offsetTop) if the returned element is the element I expect, this is tricky to detect if an element is overlapping another completely.
  3. Finally test if offsetTop and offsetLeft are located in the viewport taking scroll offsets into account.

Hope it helps.

Christophe Eblé
  • 8,071
  • 3
  • 33
  • 32
  • Could you explain document.elementFromPoint in more detail? – Macha May 24 '09 at 19:10
  • this is Mozilla's MDC summary : Returns the element from the document whose elementFromPoint method is being called which is the topmost element which lies under the given point. The point is specified via coordinates, in CSS pixels, relative to the upper-left-most point in the window or frame containing the document. – Christophe Eblé May 24 '09 at 20:58
3

This is what I have so far. It covers both 1 and 3. I'm however still struggling with 2 since I'm not that familiar with Prototype (I'm more a jQuery type of guy).

function isVisible( elem ) {
    var $elem = $(elem);

    // First check if elem is hidden through css as this is not very costly:
    if ($elem.getStyle('display') == 'none' || $elem.getStyle('visibility') == 'hidden' ) {
        //elem is set through CSS stylesheet or inline to invisible
        return false;
    }

    //Now check for the elem being outside of the viewport
    var $elemOffset = $elem.viewportOffset();
    if ($elemOffset.left < 0 || $elemOffset.top < 0) {
        //elem is left of or above viewport
        return false;
    }
    var vp = document.viewport.getDimensions();
    if ($elemOffset.left > vp.width || $elemOffset.top > vp.height) {
        //elem is below or right of vp
        return false;
    }

    //Now check for elements positioned on top:
    //TODO: Build check for this using Prototype...
    //Neither of these was true, so the elem was visible:
    return true;
}
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Pim Jager
  • 31,965
  • 17
  • 72
  • 98
2
/**
 * Checks display and visibility of elements and it's parents
 * @param  DomElement  el
 * @param  boolean isDeep Watch parents? Default is true
 * @return {Boolean}
 *
 * @author Oleksandr Knyga <oleksandrknyga@gmail.com>
 */
function isVisible(el, isDeep) {
    var elIsVisible = true;

    if("undefined" === typeof isDeep) {
        isDeep = true;
    }

    elIsVisible = elIsVisible && el.offsetWidth > 0 && el.offsetHeight > 0;

    if(isDeep && elIsVisible) {

        while('BODY' != el.tagName && elIsVisible) {
            elIsVisible = elIsVisible && 'hidden' != window.getComputedStyle(el).visibility;
            el = el.parentElement;
        }
    }

    return elIsVisible;
}
Oleksandr Knyga
  • 625
  • 9
  • 9
2

You can use the clientHeight or clientWidth properties

function isViewable(element){
  return (element.clientHeight > 0);
}
fred
  • 21
  • 1
2

Prototype's Element library is one of the most powerful query libraries in terms of the methods. I recommend you to check out the API.

A few hints:

  1. Checking visibility can be a pain, but you can use the Element.getStyle() method and Element.visible() methods combined into a custom function. With getStyle() you can check the actual computed style.

  2. I don't know exactly what you mean by "underneath" :) If you meant by it has a specific ancestor, for example, a wrapper div, you can use Element.up(cssRule):

    var child = $("myparagraph");
    if(!child.up("mywrapper")){
      // I lost my mom!
    }
    else {
      // I found my mom!
    }
    

    If you want to check the siblings of the child element you can do that too:

    var child = $("myparagraph");
    if(!child.previous("mywrapper")){
      // I lost my bro!
    } 
    else {
      // I found my bro!
    }
    
  3. Again, Element lib can help you if I understand correctly what you mean :) You can check the actual dimensions of the viewport and the offset of your element so you can calculate if your element is "off screen".

Good luck!

I pasted a test case for prototypejs at http://gist.github.com/117125. It seems in your case we simply cannot trust in getStyle() at all. For maximizing the reliability of the isMyElementReallyVisible function you should combine the following:

  • Checking the computed style (dojo has a nice implementation that you can borrow)
  • Checking the viewportoffset (prototype native method)
  • Checking the z-index for the "beneath" problem (under Internet Explorer it may be buggy)
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
yaanno
  • 211
  • 1
  • 5
  • I think I misunderstood the question completely. You want to be sure your element is visible in the viewport no matter what right? – yaanno May 23 '09 at 20:04
  • 1 and 3 are fine. You misunderstood 2. I have a few elements that are free to move around the screen. The element in question would be underneath another element if say a user dragged a toolbox over on top of it. – Macha May 24 '09 at 13:43
  • To clarify my last point, the toolbox would be a div inside the body, while the element could be a few levels deep. – Macha May 24 '09 at 13:44
1

Try element.getBoundingClientRect(). It will return an object with properties

  • bottom
  • top
  • right
  • left
  • width -- browser dependent
  • height -- browser dependent

Check that the width and height of the element's BoundingClientRect are not zero which is the value of hidden or non-visible elements. If the values are greater than zero the element should be visible in the body. Then check if the bottom property is less than screen.height which would imply that the element is withing the viewport. (Technically you would also have to account for the top of the browser window including the searchbar, buttons, etc.)

j_v_wow_d
  • 511
  • 2
  • 11
1

One way to do it is:

isVisible(elm) {
    while(elm.tagName != 'BODY') {
        if(!$(elm).visible()) return false;
        elm = elm.parentNode;
    }
    return true;
}

Credits: https://github.com/atetlaw/Really-Easy-Field-Validation/blob/master/validation.js#L178

umpirsky
  • 9,902
  • 13
  • 71
  • 96
0

I don't think checking the element's own visibility and display properties is good enough for requirement #1, even if you use currentStyle/getComputedStyle. You also have to check the element's ancestors. If an ancestor is hidden, so is the element.

0

Catch mouse-drag and viewport events (onmouseup, onresize, onscroll).

When a drag ends do a comparison of the dragged item boundary with all "elements of interest" (ie, elements with class "dont_hide" or an array of ids). Do the same with window.onscroll and window.onresize. Mark any elements hidden with a special attribute or classname or simply perform whatever action you want then and there.

The hidden tests are pretty easy. For "totally hidden" you want to know if ALL corners are either inside the dragged-item boundary or outside the viewport. For partially hidden you're looking for a single corner matching the same test.

SpliFF
  • 38,186
  • 16
  • 91
  • 120
-1

Check elements' offsetHeight property. If it is more than 0, it is visible. Note: this approach doesn't cover a situation when visibility:hidden style is set. But that style is something weird anyways.

Sergey Ilinsky
  • 31,255
  • 9
  • 54
  • 56
-1

Here is a sample script and test case. Covers positioned elements, visibilty: hidden, display: none. Didn't test z-index, assume it works.

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
  <head>
    <title></title>
    <style type="text/css">
    div {
      width: 200px;
      border: 1px solid red;
    }
    p {
      border: 2px solid green;
    }
    .r {
      border: 1px solid #BB3333;
      background: #EE9999;
      position: relative;
      top: -50px;
      height: 2em;
    }
    .of {
      overflow: hidden;
      height: 2em;
      word-wrap: none; 
    }
    .of p {
      width: 100%;
    }

    .of pre {
      display: inline;
    }
    .iv {
      visibility: hidden;
    }
    .dn {
      display: none;
    }
    </style>
    <script src="http://www.prototypejs.org/assets/2008/9/29/prototype-1.6.0.3.js"></script>
    <script>
      function isVisible(elem){
        if (Element.getStyle(elem, 'visibility') == 'hidden' || Element.getStyle(elem, 'display') == 'none') {
          return false;
        }
        var topx, topy, botx, boty;
        var offset = Element.positionedOffset(elem);
        topx = offset.left;
        topy = offset.top;
        botx = Element.getWidth(elem) + topx;
        boty = Element.getHeight(elem) + topy;
        var v = false;
        for (var x = topx; x <= botx; x++) {
          for(var y = topy; y <= boty; y++) {
            if (document.elementFromPoint(x,y) == elem) {
              // item is visible
              v = true;
              break;
            }
          }
          if (v == true) {
            break;
          }
        }
        return v;
      }

      window.onload=function() {
        var es = Element.descendants('body');
        for (var i = 0; i < es.length; i++ ) {
          if (!isVisible(es[i])) {
            alert(es[i].tagName);
          }
        }
      }
    </script>
  </head>
  <body id='body'>
    <div class="s"><p>This is text</p><p>More text</p></div>
    <div class="r">This is relative</div>
    <div class="of"><p>This is too wide...</p><pre>hidden</pre>
    <div class="iv">This is invisible</div>
    <div class="dn">This is display none</div>
  </body>
</html>
Mr. Shiny and New 安宇
  • 13,822
  • 6
  • 44
  • 64
  • Wow, every pixel? I guess if you're just pulling it in to run through a debug, but if you want to use this for anything that might have to call this multiple times in one function, you might want to include a resolution variable, to search a smaller grid of points... – Ajax Feb 28 '13 at 22:24
-2

Here is a part of the response that tells you if an element is in the viewport. You may need to check if there is nothing on top of it using elementFromPoint, but it's a bit longer.

function isInViewport(element) {
  var rect = element.getBoundingClientRect();
  var windowHeight = window.innerHeight || document.documentElement.clientHeight;
  var windowWidth = window.innerWidth || document.documentElement.clientWidth;

  return rect.bottom > 0 && rect.top < windowHeight && rect.right > 0 && rect.left < windowWidth;
}
MoOx
  • 8,423
  • 5
  • 40
  • 39