78

Possible Duplicate:
jQuery - Check if element is visible after scroling

I'm trying to determine if an element is visible on screen. In order to to this, I'm trying to find the element's vertical position using offsetTop, but the value returned is not correct. In this case, the element is not visible unless you scroll down. But despite of this, offsetTop returns a value of 618 when my screen height is 703, so according to offsetTop the element should be visible.

The code I'm using looks like this:

function posY(obj)
{
  var curtop = 0;

  if( obj.offsetParent )
  {
    while(1)
    {
      curtop += obj.offsetTop;

      if( !obj.offsetParent )
      {
        break;
      }

      obj = obj.offsetParent;
    }
  } else if( obj.y )
    {
     curtop += obj.y;
    }

  return curtop;
}

Thank you in advance!

Community
  • 1
  • 1
Robert
  • 781
  • 1
  • 6
  • 3

2 Answers2

138

--- Shameless plug ---
I have added this function to a library I created vanillajs-browser-helpers: https://github.com/Tokimon/vanillajs-browser-helpers/blob/master/inView.js
-------------------------------

Well BenM stated, you need to detect the height of the viewport + the scroll position to match up with your top position. The function you are using is ok and does the job, though its a bit more complex than it needs to be.

If you don't use jQuery then the script would be something like this:

function posY(elm) {
    var test = elm, top = 0;

    while(!!test && test.tagName.toLowerCase() !== "body") {
        top += test.offsetTop;
        test = test.offsetParent;
    }

    return top;
}

function viewPortHeight() {
    var de = document.documentElement;

    if(!!window.innerWidth)
    { return window.innerHeight; }
    else if( de && !isNaN(de.clientHeight) )
    { return de.clientHeight; }
    
    return 0;
}

function scrollY() {
    if( window.pageYOffset ) { return window.pageYOffset; }
    return Math.max(document.documentElement.scrollTop, document.body.scrollTop);
}

function checkvisible( elm ) {
    var vpH = viewPortHeight(), // Viewport Height
        st = scrollY(), // Scroll Top
        y = posY(elm);
    
    return (y > (vpH + st));
}

Using jQuery is a lot easier:

function checkVisible( elm, evalType ) {
    evalType = evalType || "visible";

    var vpH = $(window).height(), // Viewport Height
        st = $(window).scrollTop(), // Scroll Top
        y = $(elm).offset().top,
        elementHeight = $(elm).height();

    if (evalType === "visible") return ((y < (vpH + st)) && (y > (st - elementHeight)));
    if (evalType === "above") return ((y < (vpH + st)));
}

This even offers a second parameter. With "visible" (or no second parameter) it strictly checks whether an element is on screen. If it is set to "above" it will return true when the element in question is on or above the screen.

See in action: http://jsfiddle.net/RJX5N/2/

I hope this answers your question.

-- IMPROVED VERSION--

This is a lot shorter and should do it as well:

function checkVisible(elm) {
  var rect = elm.getBoundingClientRect();
  var viewHeight = Math.max(document.documentElement.clientHeight, window.innerHeight);
  return !(rect.bottom < 0 || rect.top - viewHeight >= 0);
}

with a fiddle to prove it: http://jsfiddle.net/t2L274ty/1/

And a version with threshold and mode included:

function checkVisible(elm, threshold, mode) {
  threshold = threshold || 0;
  mode = mode || 'visible';

  var rect = elm.getBoundingClientRect();
  var viewHeight = Math.max(document.documentElement.clientHeight, window.innerHeight);
  var above = rect.bottom - threshold < 0;
  var below = rect.top - viewHeight + threshold >= 0;

  return mode === 'above' ? above : (mode === 'below' ? below : !above && !below);
}

and with a fiddle to prove it: http://jsfiddle.net/t2L274ty/2/

Brien Foss
  • 2,950
  • 3
  • 16
  • 29
Tokimon
  • 3,482
  • 1
  • 16
  • 22
  • The problem is still that the initial Y-value is 618, but as soon as I start scrolling the Y-value changes to 1074, which is the correct Y-value for the element. Why is this? – Robert Mar 18 '11 at 23:19
  • 1
    @Robert: Hm that sounds very strange! do you insert any elements before that element? Or do you reposition it? But the `(vpH + st)` portion of my script should increase, if thats what you were wondering. – Tokimon Mar 20 '11 at 15:49
  • 3
    For jQuery condition should be changed with `(y> st) && (y < (vpH + st))` **OO** `(y +$(elm).height() < st + vpH)` where **OO** is `||` for partial visibility and `&&` for full visibility. – Ivan Klass Apr 22 '13 at 10:01
  • 9
    Using properly named variables is far better than using obtusely named variables with comments! – Ian Newson Apr 30 '13 at 12:25
  • 4
    Check if the whole element is visible: `function checkvisible( elm ) { var vpH = $(window).height(), // Viewport Height st = $(window).scrollTop(), // Scroll Top y = $(elm).offset().top, h = $(elm).height(); return (y > st && y+h < (vpH + st)); }` Check if any portion of the element is visible: `function checkvisible( elm ) { var vpH = $(window).height(), // Viewport Height st = $(window).scrollTop(), // Scroll Top y = $(elm).offset().top, h = $(elm).height(); return (y+h > st && y < (vpH + st)); }` – Ronny Sherer Jul 22 '13 at 15:15
  • Wouldn't this mean that, say, your footer which will have a big offset will always return true even at the top of the page and even if it's not visible? – Oscar Godson Nov 06 '13 at 02:07
  • Hey. I implemented your function and it seem, in the jQuery version, the sign is flipped. Your function returns true, when the element can't be seen. That's okay if it's meant that way, but it sure doesn't SEEM like it's meant that way. (The function is called checkVisible, not checkHidden, after all) – Kjeld Schmidt Jun 12 '14 at 05:55
  • Hm. Yes @Kjeld you seem to be right. And really i think this function needs a lot more details to be better adaptable to all sorts of situations. Fx. it doesn't take into account if the element in question is higher than the viewport height (long article section maybe), let you see if the element is fully visible on the screen or let you set a percentage/pixel amount it should be visible. So I guess there is room for a lot of improvements. – Tokimon Jun 19 '14 at 08:12
  • @Tokimon I improved it a bit. The function now returns the actual value and also offers the choice between "element is on screen" and "element is on or above screen". I made an edit to your post. – Kjeld Schmidt Jun 19 '14 at 16:20
  • @Kjeld this is a great edit. I just did my own corrections though as I prefer to use `===` (no type conversion before comparing) instead of `==` and since `eval` actually is a reserved global method (although use of it is discouraged) I changed the variable name to `evalType`. Just for good measure. But great edit otherwise. – Tokimon Jun 24 '14 at 08:23
  • Thank you very much! I wasn't even really aware of the eval method (though it does seem rather obvious in retrospect). I'm glad you like it. – Kjeld Schmidt Jun 24 '14 at 13:04
  • Great Solution. I've put this into a library so you can easily call it on any element. https://github.com/klickpush/is_hidden.js – germs12 Jan 29 '15 at 19:49
  • ouch, who would have thought such a test would be so complex. Is there a more sane way for modern browsers? – SuperUberDuper Mar 07 '16 at 16:03
  • @SuperUberDuper, I read you comment I had to agree with you so I went to investigate and found that the method `getBoundingClientRect()` had the position of the element relative to the screen, which is exactly what was needed :) So thank you for challenging my initial code, which I have updated. – Tokimon Apr 07 '16 at 08:01
  • @Tokimon you are welcome, theres also a method to check if its display none (or parent is) but I forgot how to do it – SuperUberDuper Apr 07 '16 at 12:47
  • 2
    @SuperUberDuper, again `getBoundingClientRect()` is your friend. It will return the properties set to 0 (zero) if it or one of its ancestors are display none. – Tokimon Apr 07 '16 at 12:52
  • 1
    @Tokimon, short version of this returns true when the element is fully off screen to the left, needs an additional test: return !(rect.bottom < 0 || rect.top - viewHeight >= 0 || rect.x + rect.width < 0); – enigment Mar 31 '19 at 14:38
  • hi @enigment. I assume it is the `rect.x + rect.width` part you feel has to be added. And you are right, if you want to check the horizontal plane as well... – Tokimon Apr 01 '19 at 09:33
  • This doesn't seem to work when the element is wrapped in another element with `overflow: auto`. – Steve Brush Dec 05 '19 at 19:17
22

Could you use jQuery, since it's cross-browser compatible?

function isOnScreen(element)
{
    var curPos = element.offset();
    var curTop = curPos.top;
    var screenHeight = $(window).height();
    return (curTop > screenHeight) ? false : true;
}

And then call the function using something like:

if(isOnScreen($('#myDivId'))) { /* Code here... */ };
BenM
  • 49,881
  • 23
  • 107
  • 158
  • I try this code but I got this error `Uncaught SyntaxError: Unexpected token return` from this line `(curTop > screenHeight) ? return false : return true; ` – gadss Oct 24 '12 at 10:01
  • 1
    I have edited the code and it should now function. – BenM Oct 24 '12 at 11:57
  • 14
    Why not return `!(curTop > screenHeight)` ?? – Giann Feb 06 '13 at 09:22
  • 5
    You could do, but I personally think the code above is more readable. – BenM Feb 06 '13 at 10:06
  • 2
    @BenM I can make it even more unreadable! return !(element.offset().top > $(window).height()); – Daniel Feb 27 '13 at 22:35
  • And why would you want to do that? – BenM Feb 27 '13 at 23:38
  • @BenM no reason at all! Thanks for the answer btw it worked a treat – Daniel Mar 02 '13 at 02:01
  • I just wanted to point out that this will only work if your content is displayed vertically. If you display the content horizontally with something like webkit-columns, then you would need to do something with `curLeft = curPos.left;`; – TPoschel Mar 18 '13 at 20:00
  • That's just `$('...').offset().top <= $(window).height()`, and there's no need to create a function for that. – polkovnikov.ph Oct 06 '15 at 15:22
  • 1
    That won't work because the offset() of an element doesn't change when you scroll the window – Andi Aug 15 '16 at 19:10
  • 4
    However I will vote this up because it was readable very easy to understand. It will work if you change the `var curTop = curPos.top - $(window).scrollTop();` – Andi Aug 15 '16 at 19:22