Categories
browser dom firefox html javascript

How can I tell if a DOM element is visible in the current viewport?

1181

Is there an efficient way to tell if a DOM element (in an HTML document) is currently visible (appears in the viewport)?

(The question refers to Firefox.)

8

  • 1

    Depends what you mean by visible. If you mean is it currently shown on the page, given the scroll position, you can calculate it based on the elements y offset and the current scroll position.

    – roryf

    Sep 23, 2008 at 21:29

  • 1

    I’ve added my own solution that solves this problem

    – Andy E

    Mar 4, 2013 at 14:18

  • 1

    Do any of these solutions take into account the z-index of a dom node and how that might affect visibility specifically by possibly hiding elements with a lower z-index?

    – Dexygen

    May 8, 2013 at 15:44

  • 1

    None of the answers provided work with generated absolute positioned elements.

    – thednp

    Mar 21, 2015 at 12:38

  • 3

    There are one million answers and most are ridiculously long. See here for a two-liner

    – leonheess

    Jan 10, 2020 at 14:19

406

Update: Time marches on and so have our browsers. This technique is no longer recommended and you should use Dan’s solution if you do not need to support version of Internet Explorer before 7.

Original solution (now outdated):

This will check if the element is entirely visible in the current viewport:

function elementInViewport(el) {
  var top = el.offsetTop;
  var left = el.offsetLeft;
  var width = el.offsetWidth;
  var height = el.offsetHeight;

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

  return (
    top >= window.pageYOffset &&
    left >= window.pageXOffset &&
    (top + height) <= (window.pageYOffset + window.innerHeight) &&
    (left + width) <= (window.pageXOffset + window.innerWidth)
  );
}

You could modify this simply to determine if any part of the element is visible in the viewport:

function elementInViewport2(el) {
  var top = el.offsetTop;
  var left = el.offsetLeft;
  var width = el.offsetWidth;
  var height = el.offsetHeight;

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

  return (
    top < (window.pageYOffset + window.innerHeight) &&
    left < (window.pageXOffset + window.innerWidth) &&
    (top + height) > window.pageYOffset &&
    (left + width) > window.pageXOffset
  );
}

11

  • 1

    Original function posted had a mistake. Needed to save the width/height before reassigning el…

    – Prestaul

    Sep 24, 2008 at 2:56

  • 29

    What if the element lives in a scrollable div and scrolled out of a view??

    – amartynov

    Mar 6, 2011 at 8:42

  • 3

    Please review a newer version of the script below

    – Dan

    Sep 26, 2011 at 15:29

  • 1

    Also curious about @amartynov’s question. Anyone know how to simply tell if an element is hidden due to overflow of an ancestor element? Bonus if this can be detected regardless of how deeply nested the child is.

    Nov 7, 2012 at 23:51

  • 1

    @deadManN recursing through the DOM is notoriously slow. That is reason enough, but the browser vendors have also created getBoundingClientRect for specifically the purpose of finding element coordinates… Why wouldn’t we use it?

    – Prestaul

    Jul 6, 2015 at 15:25

406

Update: Time marches on and so have our browsers. This technique is no longer recommended and you should use Dan’s solution if you do not need to support version of Internet Explorer before 7.

Original solution (now outdated):

This will check if the element is entirely visible in the current viewport:

function elementInViewport(el) {
  var top = el.offsetTop;
  var left = el.offsetLeft;
  var width = el.offsetWidth;
  var height = el.offsetHeight;

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

  return (
    top >= window.pageYOffset &&
    left >= window.pageXOffset &&
    (top + height) <= (window.pageYOffset + window.innerHeight) &&
    (left + width) <= (window.pageXOffset + window.innerWidth)
  );
}

You could modify this simply to determine if any part of the element is visible in the viewport:

function elementInViewport2(el) {
  var top = el.offsetTop;
  var left = el.offsetLeft;
  var width = el.offsetWidth;
  var height = el.offsetHeight;

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

  return (
    top < (window.pageYOffset + window.innerHeight) &&
    left < (window.pageXOffset + window.innerWidth) &&
    (top + height) > window.pageYOffset &&
    (left + width) > window.pageXOffset
  );
}

11

  • 1

    Original function posted had a mistake. Needed to save the width/height before reassigning el…

    – Prestaul

    Sep 24, 2008 at 2:56

  • 29

    What if the element lives in a scrollable div and scrolled out of a view??

    – amartynov

    Mar 6, 2011 at 8:42

  • 3

    Please review a newer version of the script below

    – Dan

    Sep 26, 2011 at 15:29

  • 1

    Also curious about @amartynov’s question. Anyone know how to simply tell if an element is hidden due to overflow of an ancestor element? Bonus if this can be detected regardless of how deeply nested the child is.

    Nov 7, 2012 at 23:51

  • 1

    @deadManN recursing through the DOM is notoriously slow. That is reason enough, but the browser vendors have also created getBoundingClientRect for specifically the purpose of finding element coordinates… Why wouldn’t we use it?

    – Prestaul

    Jul 6, 2015 at 15:25

224

Update

In modern browsers, you might want to check out the Intersection Observer API which provides the following benefits:

  • Better performance than listening for scroll events
  • Works in cross domain iframes
  • Can tell if an element is obstructing/intersecting another

Intersection Observer is on its way to being a full-fledged standard and is already supported in Chrome 51+, Edge 15+ and Firefox 55+ and is under development for Safari. There’s also a polyfill available.


Previous answer

There are some issues with the answer provided by Dan that might make it an unsuitable approach for some situations. Some of these issues are pointed out in his answer near the bottom, that his code will give false positives for elements that are:

  • Hidden by another element in front of the one being tested
  • Outside the visible area of a parent or ancestor element
  • An element or its children hidden by using the CSS clip property

These limitations are demonstrated in the following results of a simple test:

Failed test, using isElementInViewport

The solution: isElementVisible()

Here’s a solution to those problems, with the test result below and an explanation of some parts of the code.

function isElementVisible(el) {
    var rect     = el.getBoundingClientRect(),
        vWidth   = window.innerWidth || document.documentElement.clientWidth,
        vHeight  = window.innerHeight || document.documentElement.clientHeight,
        efp      = function (x, y) { return document.elementFromPoint(x, y) };     

    // Return false if it's not in the viewport
    if (rect.right < 0 || rect.bottom < 0 
            || rect.left > vWidth || rect.top > vHeight)
        return false;

    // Return true if any of its four corners are visible
    return (
          el.contains(efp(rect.left,  rect.top))
      ||  el.contains(efp(rect.right, rect.top))
      ||  el.contains(efp(rect.right, rect.bottom))
      ||  el.contains(efp(rect.left,  rect.bottom))
    );
}

Passing test: http://jsfiddle.net/AndyE/cAY8c/

And the result:

Passed test, using isElementVisible

Additional notes

This method is not without its own limitations, however. For instance, an element being tested with a lower z-index than another element at the same location would be identified as hidden even if the element in front doesn’t actually hide any part of it. Still, this method has its uses in some cases that Dan’s solution doesn’t cover.

Both element.getBoundingClientRect() and document.elementFromPoint() are part of the CSSOM Working Draft specification and are supported in at least IE 6 and later and most desktop browsers for a long time (albeit, not perfectly). See Quirksmode on these functions for more information.

contains() is used to see if the element returned by document.elementFromPoint() is a child node of the element we’re testing for visibility. It also returns true if the element returned is the same element. This just makes the check more robust. It’s supported in all major browsers, Firefox 9.0 being the last of them to add it. For older Firefox support, check this answer’s history.

If you want to test more points around the element for visibility―ie, to make sure the element isn’t covered by more than, say, 50%―it wouldn’t take much to adjust the last part of the answer. However, be aware that it would probably be very slow if you checked every pixel to make sure it was 100% visible.

20

  • 3

    Did you mean to use doc.documentElement.clientWidth? Should that be ‘document.documentElement’ instead? On a different note, this is the only method the also works for use cases like hiding the content of an element for accessibility using the CSS ‘clip’ property: snook.ca/archives/html_and_css/hiding-content-for-accessibility

    Mar 19, 2013 at 19:34


  • 3

    For me it is not working. But inViewport() in previous answer is working in FF.

    Dec 28, 2013 at 7:05

  • 14

    It may also be beneficial to check that the center of the element is visible if you have rounded corners or a transform applied, as the bounding corners may not return the expected element: element.contains(efp(rect.right - (rect.width / 2), rect.bottom - (rect.height / 2)))

    – Jared

    Mar 10, 2015 at 8:15


  • 2

    Did not work on inputs for me (chrome canary 50). Not sure why, maybe native rounder corners ? I had to reduce the coords slightly to make it work el.contains(efp(rect.left+1, rect.top+1)) || el.contains(efp(rect.right-1, rect.top+1)) || el.contains(efp(rect.right-1, rect.bottom-1)) || el.contains(efp(rect.left+1, rect.bottom-1))

    – Rayjax

    Feb 18, 2016 at 14:05

  • 1

    @AndyE This wouldn’t always work for elements that are wider/taller than the viewport, because all corners could be outside the screen even though it’s visible

    – Andy

    Jul 19, 2016 at 19:02