1258

I'm loading elements via AJAX. Some of them are only visible if you scroll down the page. Is there any way I can know if an element is now in the visible part of the page?

K-Gun
  • 10,121
  • 2
  • 50
  • 56
yoavf
  • 19,315
  • 9
  • 34
  • 37
  • I don't really get the problem. Could you try to add more info? – Natrium Jan 28 '09 at 10:04
  • 52
    he means that he wants a method to know if a given element is displayed in the browser window, or if the user needs to scroll to see it. – Romain Linsolas Jan 28 '09 at 10:14
  • 1
    To check if an element is fully visible in a container, just add an extra selector param and re-use the elem code for it. `Library.IsElementVisibleInContainer = function (elementSelector, containerSelector) { var containerViewTop = $(containerSelector).offset().top; var containerViewBottom = containerViewTop + $(containerSelector).height();` – Lifes Oct 30 '12 at 11:25
  • what would be this "Library" @Lindsay ? – periback2 Feb 15 '13 at 19:13
  • 4
    possible duplicate of [How to tell if a DOM element is visible in the current viewport?](http://stackoverflow.com/questions/123999/how-to-tell-if-a-dom-element-is-visible-in-the-current-viewport) – Audrius Meskauskas Feb 20 '13 at 20:27
  • The library is just a function I created. E.g. `function Library() { }`. I write my javascript this way because I find it easier to read/maintain versus just having a function (e.g. IsElementVisibleInContainer) and having no idea where it came from. – Lifes Feb 21 '13 at 20:26
  • intersection observer will be the answer... but not quite yet unless you use a polyfill for Safari and IE. Observer is a new version of Event. This will fire a callback when nn% of the element is visible. See https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API – Andrew Murphy May 24 '18 at 06:32
  • 1
    Possible duplicate of [How to tell if a DOM element is visible in the current viewport?](https://stackoverflow.com/questions/123999/how-to-tell-if-a-dom-element-is-visible-in-the-current-viewport) – Sumit Jul 19 '18 at 06:28
  • 1
    All answers will trigger reflow so it could be bottle neck, you shout use [IntersectionObserver](https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API) if supported. It will have better performance on modern browsers, – jcubic Apr 06 '19 at 19:51
  • Ready non-perfect solution is here: https://github.com/proxy-m/real-visibility – user1742529 Jan 02 '21 at 13:12

44 Answers44

1302

This should do the trick:

function isScrolledIntoView(elem)
{
    var docViewTop = $(window).scrollTop();
    var docViewBottom = docViewTop + $(window).height();

    var elemTop = $(elem).offset().top;
    var elemBottom = elemTop + $(elem).height();

    return ((elemBottom <= docViewBottom) && (elemTop >= docViewTop));
}

Simple Utility Function This will allow you to call a utility function that accepts the element you're looking for and if you want the element to be fully in view or partially.

function Utils() {

}

Utils.prototype = {
    constructor: Utils,
    isElementInView: function (element, fullyInView) {
        var pageTop = $(window).scrollTop();
        var pageBottom = pageTop + $(window).height();
        var elementTop = $(element).offset().top;
        var elementBottom = elementTop + $(element).height();

        if (fullyInView === true) {
            return ((pageTop < elementTop) && (pageBottom > elementBottom));
        } else {
            return ((elementTop <= pageBottom) && (elementBottom >= pageTop));
        }
    }
};

var Utils = new Utils();

Usage

var isElementInView = Utils.isElementInView($('#flyout-left-container'), false);

if (isElementInView) {
    console.log('in view');
} else {
    console.log('out of view');
}
Andrea Casaccia
  • 4,384
  • 4
  • 24
  • 51
Scott Dowding
  • 13,327
  • 1
  • 14
  • 10
  • 58
    Note that this only works if the document is the element being scrolled, i.e. you aren't checking visibility of some element inside a scrolling inner pane. – Andrew B. Feb 12 '13 at 20:29
  • 8
    how to add a little offset? – Jürgen Paul Sep 09 '13 at 18:48
  • 5
    Only worked when I used `window.innerHeight` instead – Christian Schnorr Jan 17 '14 at 14:30
  • 2
    For `elemTop` I used `$(elem).position().top` and for `elemBottom` I used `elemTop + $(elem).outerHeight(true)`. – Sarah Vessels Jan 26 '14 at 17:18
  • 2
    Also, using `&&` tests if the entire element is in view. If you want to know whether any part of the element is in view, use `||`. – Sarah Vessels Jan 26 '14 at 17:28
  • 1
    As of 06/06/2014, I just could not get this to work properly for Firefox 29. "$(elem).offset().top" will constantly return a static result, always the same even after scrolling. I ended up finding a solution that uses the vanilla javascript method getBoundingClientRect() – TrippinBilly Jun 07 '14 at 03:07
  • 14
    For: "Any part of the element in view", I used: ((( elemTop >= docViewTop) && (elemTop <= docViewBottom)) || ((elemBottom >= docViewTop) && (elemBottom <= docViewBottom))) – Grizly Jul 29 '14 at 23:42
  • i found combining two options works best - `return ((elemBottom >= docViewBottom) && (elemTop <= docViewTop)) || ((elemBottom <= docViewBottom) && (elemTop >= docViewTop));` – Sagiv Ofek Dec 17 '14 at 22:41
  • 2
    For "any part of element in view", `((elemTop <= docViewBottom) && (elemBottom >= docViewTop))` covers all cases completely - even if the top and bottom edges are offscreen and only the middle is in view. – CodeMoose May 13 '15 at 19:55
  • For "any part of element in view" `Math.min(elemBottom, docViewBottom) >= Math.max(elemeTop, docViewTop);`. – jorar91 Sep 30 '15 at 20:40
  • 1
    @GauravAggarwal This answer is jQuery. – gcampbell Jun 06 '16 at 19:39
  • This code works! You can even replace `$(window)` with `$(element).scrollParent()` from jQuery UI, and this works with scrollable elements (i.e., overflow-y:scroll) as well. – Jordan Aug 01 '16 at 19:56
  • This is not working when I consider event.target as $(window). I have
    – user2934433 Mar 18 '17 at 21:51
  • It does not seem to work in all cases. For example, even after I scrolled to the very bottom of the windows, I get this: `docViewTop: 496, docViewBottom: 1039, elemTop: 1029.3125, elemBottom: 1039.3125` and the function returns `false` (= it is not visible). – Antonio Sesto Feb 16 '18 at 08:08
  • This doesn't work with floating elements. It doesn't take into account the element's absolute position, and incorrectly thinks its scrolled offscreen when it hasn't. – Cerin Jun 14 '18 at 20:06
  • 1
    Scott, would you consider updating this answer? This seems to be no longer the preferred way to handle a visibility check. Maybe mention the [Intersection Observer API](https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API). – Andreas Haferburg May 31 '20 at 10:32
  • I think you have a bug in your logic there. Instead of `return ((elemBottom <= docViewBottom) && (elemTop >= docViewTop));` surely it should say `return ((elemTop <= docViewBottom) && (elemBottom >= docViewTop));`. The way you have it now, it won’t trigger until it’s *fully* in view, which means that it will never trigger if the element is taller than the viewport. – Timwi Jul 31 '20 at 00:20
462

This answer in Vanilla:

function isScrolledIntoView(el) {
    var rect = el.getBoundingClientRect();
    var elemTop = rect.top;
    var elemBottom = rect.bottom;

    // Only completely visible elements return true:
    var isVisible = (elemTop >= 0) && (elemBottom <= window.innerHeight);
    // Partially visible elements return true:
    //isVisible = elemTop < window.innerHeight && elemBottom >= 0;
    return isVisible;
}
jcubic
  • 51,975
  • 42
  • 183
  • 323
bravedick
  • 6,554
  • 2
  • 22
  • 45
  • 30
    shouldn't this be `isVisible = elementTop < window.innerHeight && elementBottom >= 0`? Otherwise an element half on the screen returns false. – gman Feb 23 '15 at 21:08
  • 10
    no. i check if some element is fully visible on the page. if you want to check visibility of some part - you can customise this snippet. – bravedick Feb 24 '15 at 08:45
  • 16
    I find this answer to perform better than the chosen answer. Simpler too. – Adam Venezia Jul 15 '15 at 16:54
  • 1
    the element.getBoundingClientRect bottom and top gave me the right idea. +1 – Jony-Y Jan 01 '16 at 11:51
  • 13
    In comparison to the approved answer, this performs waaaay much better with hundreds of elements. – ncla Feb 15 '16 at 00:05
  • 3
    Is there any particular reason jQuery is favoured over vanilla on SO? – gcampbell Jun 06 '16 at 19:40
  • 1
    @gcampbell Yes, people are afraid of using the DOM or don't know the API or just plain forget it exists. Even though in many cases jQuery is not needed anymore it has unfortunately become a de facto standard. In all fairness, the DOM like most web technologies is not fun to use, at all. – Marc Diethelm Jun 30 '16 at 16:26
  • @MarcDiethelm Especially with namespaces e.g. SVG. – gcampbell Jun 30 '16 at 20:28
  • the function will return always false if the element is visible but it's wider than the scroll area – guari Jul 22 '16 at 12:19
  • 1
    @guari sure, cause visible here means visible, and not partially visible ;) – bravedick Jul 25 '16 at 19:57
  • 2
    I'd cache the result of `el.getBoundingClientRect()` in a variable instead of calling it twice to squeeze out some free performance boost. – SkyWriter Feb 16 '17 at 05:57
  • 5
    see a small fiddle demonstrating here - https://jsfiddle.net/shaaraddalvi/4rp09jL0/ – upInCloud Apr 03 '17 at 10:25
  • `getBoundingClientRect()` [causes](https://gist.github.com/paulirish/5d52fb081b3570c81e3a) "layout thrashing". Any suggestion how to make scrolling in this respect performant? – kyw Jul 27 '18 at 10:10
  • 2
    is this plain js, showing an error getboundingclientrect not a functuon – Maduekwe Pedro Mar 29 '20 at 23:33
  • Would love a version of this that allows for checking against visibility in a scrolling context other than the entire window. – Nathan Arthur May 26 '21 at 16:28
129

Update: use IntersectionObserver


The best method I have found so far is the jQuery appear plugin. Works like a charm.

Mimics a custom "appear" event, which fires when an element scrolls into view or otherwise becomes visible to the user.

$('#foo').appear(function() {
  $(this).text('Hello world');
});

This plugin can be used to prevent unnecessary requests for content that's hidden or outside the viewable area.

Joe Lencioni
  • 9,595
  • 17
  • 50
  • 65
  • 32
    This is a cool plugin, no doubt, but doesn't answer the question. – Jon Adams Nov 27 '10 at 22:21
  • 5
    While the jQuery-appear plugin is good for content on the main page area, it unfortunately has issues with fixed size scrolling divs with overflow. The event can fire prematurely when the bound element is within the page's view-able area but outside the div's viewable area and then not fire as expected when the element comes into view in the div. – Peter Aug 12 '11 at 13:46
  • 17
    Is there a disappear plugin? – Shamoon Feb 05 '12 at 23:53
  • 3
    @Shamoon check the source for the `appear plugin` and you will probably just need to add a `!` somewhere to get a `disappear` plugin. – Lucky Soni Mar 11 '14 at 14:48
  • 1
    check the demo of appear plugin here http://morr.github.io/appear.html it also have a disappear functionality.. – Hammad May 23 '14 at 07:52
  • 5
    As a note, this doesn't work with jQuery 1.11.X https://github.com/morr/jquery.appear/issues/37 – Jason Parham Mar 17 '15 at 00:47
  • @Shamoon : $('someselector').on('disappear', function(event, $all_disappeared_elements) { // this element is now outside browser viewport }); – malix Dec 03 '15 at 03:14
92

Here's my pure JavaScript solution that works if it's hidden inside a scrollable container too.

Demo here (try resizing the window too)

var visibleY = function(el){
  var rect = el.getBoundingClientRect(), top = rect.top, height = rect.height, 
    el = el.parentNode
  // Check if bottom of the element is off the page
  if (rect.bottom < 0) return false
  // Check its within the document viewport
  if (top > document.documentElement.clientHeight) return false
  do {
    rect = el.getBoundingClientRect()
    if (top <= rect.bottom === false) return false
    // Check if the element is out of view due to a container scrolling
    if ((top + height) <= rect.top) return false
    el = el.parentNode
  } while (el != document.body)
  return true
};

EDIT 2016-03-26: I've updated the solution to account for scrolling past the element so it's hidden above the top of the scroll-able container. EDIT 2018-10-08: Updated to handle when scrolled out of view above the screen.

Ally
  • 4,256
  • 5
  • 31
  • 40
  • thanks, maybe better be `return top <= document.documentElement.clientHeight && top >= 0;` – Yousef Salimpour Jun 15 '14 at 08:53
  • 18
    +1 This was the only coded (i.e. not third party) answer that takes into account the recursive nature of elements. I've expanded to handle horizontal, vertical and page scroll: http://jsfiddle.net/9nuqpgqa/ – Pebbl Oct 03 '14 at 12:24
  • 4
    This solution only checks top of the element. If first top pixel is visible, it will return true even if the rest of the item is not visible. To check if entire element is visible, you need to check bottom property too. – Wojciech Jakubas Feb 07 '16 at 10:36
  • 2
    Aye, neat! Used to help write [this answer](http://stackoverflow.com/a/42246594/3478010) (with credit as js comment). – Roamer-1888 Feb 15 '17 at 12:06
  • Missing ; after the second "return false" in the loop – Mikhail Ramendik Oct 19 '17 at 00:07
  • Where in that code do I have to place my function whcih I want to call when the element is visible? – KSPR Sep 06 '18 at 12:51
  • There are edge cases that this solution just do not work. – mr1031011 Oct 26 '18 at 17:10
86

Using IntersectionObserver API

(native in modern browsers)


It's easy & efficient to determine if an element is visible in the viewport, or in any scrollable container, by using an observer.

The need to attach a scroll event and manually checking on the event callback is eliminated, which is more efficient:

// define an observer instance
var observer = new IntersectionObserver(onIntersection, {
  root: null,   // default is the viewport
  threshold: .5 // percentage of taregt's visible area. Triggers "onIntersection"
})

// callback is called on intersection change
function onIntersection(entries, opts){
  entries.forEach(entry =>  
    entry.target.classList.toggle('visible', entry.isIntersecting)
  )
}

// Use the bserver to observe an element
observer.observe( document.querySelector('.box') )

// To stop observing:
// observer.unobserve(entry.target)
span{ position:fixed; top:0; left:0; }
.box{ width:100px; height:100px; background:red; margin:1000px; transition:.75s; }
.box.visible{ background:green; border-radius:50%; }
<span>Scroll both Vertically &amp; Horizontally...</span>
<div class='box'></div>

View browsers support table (not supported in IE/Safari)

vsync
  • 87,559
  • 45
  • 247
  • 317
42

jQuery Waypoints plugin goes very nice here.

$('.entry').waypoint(function() {
   alert('You have scrolled to an entry.');
});

There are some examples on the site of the plugin.

Fedir RYKHTIK
  • 9,110
  • 6
  • 53
  • 64
  • 3
    For me it only worked with an offset `$('#my-div').waypoint(function() { console.log('Hello there!'); }, { offset: '100%' });` – leymannx Jun 09 '15 at 13:20
35

Plain vanilla to check if element (el) is visible in scrollable div (holder)

function isElementVisible (el, holder) {
  holder = holder || document.body
  const { top, bottom, height } = el.getBoundingClientRect()
  const holderRect = holder.getBoundingClientRect()

  return top <= holderRect.top
    ? holderRect.top - top <= height
    : bottom - holderRect.bottom <= height
}

Usage with jQuery:

var el = $('tr:last').get(0);
var holder = $('table').get(0);
var isVisible = isScrolledIntoView(el, holder);
K-Gun
  • 10,121
  • 2
  • 50
  • 56
Denis Matafonov
  • 2,260
  • 17
  • 27
  • 4
    In this era of Single Page Applications it has become more common to check if an element is visible within some other element besides **window**. That's why this one gets my upvote. – H Dog Feb 10 '20 at 17:10
22

How about

function isInView(elem){
   return $(elem).offset().top - $(window).scrollTop() < $(elem).height() ;
}

After that you can trigger whatever you want once the element is in view like this

$(window).scroll(function(){
   if (isInView($('.classOfDivToCheck')))
      //fire whatever you what 
      dothis();
})

That works for me just fine

sectus
  • 14,914
  • 5
  • 49
  • 88
webicy
  • 1,407
  • 1
  • 11
  • 14
  • 1
    This works for me, but I used the, seemingly more complete, function isScrolledIntoView at http://stackoverflow.com/questions/487073/check-if-element-is-visible-after-scrolling :) – Meetai.com Jul 06 '14 at 06:58
  • 3
    I think it should be $(window).scrollTop() < $(elem).offset().top + $(elem).height() ; – Young Apr 07 '15 at 22:23
  • My modification would be like this: ` return $( window ).scrollTop() + $( window ).height() > $( elem ).offset().top + $( elem ).height(); ` – bubencode Sep 02 '18 at 21:06
16

Tweeked Scott Dowding's cool function for my requirement- this is used for finding if the element has just scrolled into the screen i.e it's top edge .

function isScrolledIntoView(elem)
{
    var docViewTop = $(window).scrollTop();
    var docViewBottom = docViewTop + $(window).height();
    var elemTop = $(elem).offset().top;
    return ((elemTop <= docViewBottom) && (elemTop >= docViewTop));
}
Snigdha Batra
  • 797
  • 7
  • 16
15

WebResourcesDepot wrote a script to load while scrolling that uses jQuery some time ago. You can view their Live Demo Here. The beef of their functionality was this:

$(window).scroll(function(){
  if  ($(window).scrollTop() == $(document).height() - $(window).height()){
    lastAddedLiveFunc();
  }
});

function lastAddedLiveFunc() { 
  $('div#lastPostsLoader').html('<img src="images/bigLoader.gif">');
  $.post("default.asp?action=getLastPosts&lastPostID="+$(".wrdLatest:last").attr("id"),
    function(data){
        if (data != "") {
          $(".wrdLatest:last").after(data);         
        }
      $('div#lastPostsLoader').empty();
    });
};
Sampson
  • 251,934
  • 70
  • 517
  • 549
9

Most answers here don't take into account that an element can also be hidden because it is scrolled out of view of a div, not only of the whole page.

To cover that possibility, you basically have to check if the element is positioned inside the bounds of each of its parents.

This solution does exactly that:

function(element, percentX, percentY){
    var tolerance = 0.01;   //needed because the rects returned by getBoundingClientRect provide the position up to 10 decimals
    if(percentX == null){
        percentX = 100;
    }
    if(percentY == null){
        percentY = 100;
    }

    var elementRect = element.getBoundingClientRect();
    var parentRects = [];

    while(element.parentElement != null){
        parentRects.push(element.parentElement.getBoundingClientRect());
        element = element.parentElement;
    }

    var visibleInAllParents = parentRects.every(function(parentRect){
        var visiblePixelX = Math.min(elementRect.right, parentRect.right) - Math.max(elementRect.left, parentRect.left);
        var visiblePixelY = Math.min(elementRect.bottom, parentRect.bottom) - Math.max(elementRect.top, parentRect.top);
        var visiblePercentageX = visiblePixelX / elementRect.width * 100;
        var visiblePercentageY = visiblePixelY / elementRect.height * 100;
        return visiblePercentageX + tolerance > percentX && visiblePercentageY + tolerance > percentY;
    });
    return visibleInAllParents;
};

It also lets you specify to what percentage it has to be visible in each direction.
It doesn't cover the possibility that it may be hidden due to other factors, like display: hidden.

This should work in all major browsers, since it only uses getBoundingClientRect. I personally tested it in Chrome and Internet Explorer 11.

Domysee
  • 11,862
  • 10
  • 48
  • 75
  • Thank you for this code. I wonder how you would add the event listener on scroll in this case that you have multiple nested scrollable elements? It seems like adding the listener to window alone is not enough, do we have to traverse back to the top parent to add the listener to each scrollable container? – mr1031011 Feb 13 '18 at 11:41
  • @mr1031011 It should be possible to add the handler to window and then check for the target to identify the container that was scrolled. – Domysee Feb 13 '18 at 13:59
  • right, it doesn't work with the example given by @vanowm, – mr1031011 Oct 26 '18 at 17:31
8

isScrolledIntoView is a very needful function, so I tried it, it works for elements not heigher than the viewport, but if the element is bigger as the viewport it does not work. To fix this easily change the condition

return ((elemBottom <= docViewBottom) && (elemTop >= docViewTop));

to this:

return (docViewBottom >= elemTop && docViewTop <= elemBottom);

See demo here: http://jsfiddle.net/RRSmQ/

Robert
  • 618
  • 8
  • 22
8

Here is another solution:

<script type="text/javascript">
$.fn.is_on_screen = function(){
    var win = $(window);
    var viewport = {
        top : win.scrollTop(),
        left : win.scrollLeft()
    };
    viewport.right = viewport.left + win.width();
    viewport.bottom = viewport.top + win.height();

    var bounds = this.offset();
    bounds.right = bounds.left + this.outerWidth();
    bounds.bottom = bounds.top + this.outerHeight();

    return (!(viewport.right < bounds.left || viewport.left > bounds.right ||    viewport.bottom < bounds.top || viewport.top > bounds.bottom));
 };

if( $('.target').length > 0 ) { // if target element exists in DOM
    if( $('.target').is_on_screen() ) { // if target element is visible on screen after DOM loaded
        $('.log').html('<div class="alert alert-success">target element is visible on screen</div>'); // log info       
    } else {
        $('.log').html('<div class="alert">target element is not visible on screen</div>'); // log info
    }
}
$(window).on('scroll', function(){ // bind window scroll event
if( $('.target').length > 0 ) { // if target element exists in DOM
    if( $('.target').is_on_screen() ) { // if target element is visible on screen after DOM loaded
        $('.log').html('<div class="alert alert-success">target element is visible on screen</div>'); // log info
    } else {
        $('.log').html('<div class="alert">target element is not visible on screen</div>'); // log info
    }
}
});
</script>

See it in JSFiddle

Adrian P.
  • 4,484
  • 1
  • 40
  • 43
7

There is a plugin for jQuery called inview which adds a new "inview" event.


Here is some code for a jQuery plugin that doesn't use events:

$.extend($.expr[':'],{
    inView: function(a) {
        var st = (document.documentElement.scrollTop || document.body.scrollTop),
            ot = $(a).offset().top,
            wh = (window.innerHeight && window.innerHeight < $(window).height()) ? window.innerHeight : $(window).height();
        return ot > st && ($(a).height() + ot) < (st + wh);
    }
});

(function( $ ) {
    $.fn.inView = function() {
        var st = (document.documentElement.scrollTop || document.body.scrollTop),
        ot = $(this).offset().top,
        wh = (window.innerHeight && window.innerHeight < $(window).height()) ? window.innerHeight : $(window).height();

        return ot > st && ($(this).height() + ot) < (st + wh);
    };
})( jQuery );

I found this in a comment here ( http://remysharp.com/2009/01/26/element-in-view-event-plugin/ ) by a bloke called James

ness-EE
  • 1,057
  • 11
  • 17
7
function isScrolledIntoView(elem) {
    var docViewTop = $(window).scrollTop(),
        docViewBottom = docViewTop + $(window).height(),
        elemTop = $(elem).offset().top,
     elemBottom = elemTop + $(elem).height();
   //Is more than half of the element visible
   return ((elemTop + ((elemBottom - elemTop)/2)) >= docViewTop && ((elemTop + ((elemBottom - elemTop)/2)) <= docViewBottom));
}
Pascal Gagneur
  • 142
  • 1
  • 4
7

This considers any padding, border or margin the element has as well as elements larger than the viewport itself.

function inViewport($ele) {
    var lBound = $(window).scrollTop(),
        uBound = lBound + $(window).height(),
        top = $ele.offset().top,
        bottom = top + $ele.outerHeight(true);

    return (top > lBound && top < uBound)
        || (bottom > lBound && bottom < uBound)
        || (lBound >= top && lBound <= bottom)
        || (uBound >= top && uBound <= bottom);
}

To call it use something like this:

var $myElement = $('#my-element'),
    canUserSeeIt = inViewport($myElement);

console.log(canUserSeeIt); // true, if element is visible; false otherwise
Brent Barbata
  • 3,331
  • 3
  • 22
  • 20
7

Building off of this great answer, you can simplify it a little further using ES2015+:

function isScrolledIntoView(el) {
  const { top, bottom } = el.getBoundingClientRect()
  return top >= 0 && bottom <= window.innerHeight
}

If you don't care about the top going out of the window and just care that the bottom has been viewed, this can be simplified to

function isSeen(el) {
  return el.getBoundingClientRect().bottom <= window.innerHeight
}

or even the one-liner

const isSeen = el => el.getBoundingClientRect().bottom <= window.innerHeight
Community
  • 1
  • 1
rpearce
  • 1,554
  • 1
  • 20
  • 26
  • This answer like most assumes that the scroll component it the window. An element can be scrollable inside some other element. You do not account for that. – lostintranslation Dec 03 '20 at 22:39
  • @lostintranslation The original question states "Is there any way I can know if an element is now in the visible part of the page?", so an answer here doesn't require your constraint. – rpearce Dec 04 '20 at 19:54
  • you are certainly correct, an answer here does not require that. That being said meeting minimum requirements often yields minimum upvotes and help for others trying to troubleshoot problems. Just pointing out the assumption made, in case it helps others. – lostintranslation Dec 04 '20 at 19:59
6

I needed to check visibility in elements inside scrollable DIV container

    //p = DIV container scrollable
    //e = element
    function visible_in_container(p, e) {
        var z = p.getBoundingClientRect();
        var r = e.getBoundingClientRect();

        // Check style visiblilty and off-limits
        return e.style.opacity > 0 && e.style.display !== 'none' &&
               e.style.visibility !== 'hidden' &&
               !(r.top > z.bottom || r.bottom < z.top ||
                 r.left > z.right || r.right < z.left);
    }
Brett Zamir
  • 12,481
  • 5
  • 45
  • 68
Pigmalión
  • 79
  • 1
  • 2
  • this works for me if I change the `e.style.opacity > 0` to `(!e.style.opacity || e.style.opacity > 0)` because by default it is the empty string for me in FF. – Brett Zamir Jun 21 '14 at 01:10
4

If you want to tweak this for scrolling item within another div,

function isScrolledIntoView (elem, divID) 

{

    var docViewTop = $('#' + divID).scrollTop();


    var docViewBottom = docViewTop + $('#' + divID).height();

    var elemTop = $(elem).offset().top;
    var elemBottom = elemTop + $(elem).height();

    return ((elemBottom <= docViewBottom) && (elemTop >= docViewTop)); 
}
4

You can make use of jquery plugin "onScreen" to check if the element is in the current viewport when you scroll. The plugin sets the ":onScreen" of the selector to true when the selector appears on the screen. This is the link for the plugin which you can include in your project. "http://benpickles.github.io/onScreen/jquery.onscreen.min.js"

You can try the below example which works for me.

$(document).scroll(function() {
    if($("#div2").is(':onScreen')) {
        console.log("Element appeared on Screen");
        //do all your stuffs here when element is visible.
    }
    else {
        console.log("Element not on Screen");
        //do all your stuffs here when element is not visible.
    }
});

HTML Code:

<div id="div1" style="width: 400px; height: 1000px; padding-top: 20px; position: relative; top: 45px"></div> <br>
<hr /> <br>
<div id="div2" style="width: 400px; height: 200px"></div>

CSS:

#div1 {
    background-color: red;
}
#div2 {
    background-color: green;
}
Vasuki Dileep
  • 485
  • 3
  • 7
4

A more efficient version of this answer:

 /**
 * Is element within visible region of a scrollable container
 * @param {HTMLElement} el - element to test
 * @returns {boolean} true if within visible region, otherwise false
 */
 function isScrolledIntoView(el) {
      var rect = el.getBoundingClientRect();
      return (rect.top >= 0) && (rect.bottom <= window.innerHeight);
 }
John Doherty
  • 2,318
  • 22
  • 32
4

I have such a method in my application, but it does not use jQuery:

/* Get the TOP position of a given element. */
function getPositionTop(element){
    var offset = 0;
    while(element) {
        offset += element["offsetTop"];
        element = element.offsetParent;
    }
    return offset;
}

/* Is a given element is visible or not? */
function isElementVisible(eltId) {
    var elt = document.getElementById(eltId);
    if (!elt) {
        // Element not found.
        return false;
    }
    // Get the top and bottom position of the given element.
    var posTop = getPositionTop(elt);
    var posBottom = posTop + elt.offsetHeight;
    // Get the top and bottom position of the *visible* part of the window.
    var visibleTop = document.body.scrollTop;
    var visibleBottom = visibleTop + document.documentElement.offsetHeight;
    return ((posBottom >= visibleTop) && (posTop <= visibleBottom));
}

Edit : This method works well for I.E. (at least version 6). Read the comments for compatibility with FF.

Romain Linsolas
  • 73,921
  • 45
  • 197
  • 265
  • 2
    For some reason document.body.scrollTop always returns 0 (on ff3). Change it to var visibleTop = (document.documentElement.scrollTop?document.documentElement.scrollTop:document.body.scrollTop); – yoavf Jan 28 '09 at 13:40
  • Sorry for that. My application must run only in IE 6 (yes, I am not lucky :( ), so I never tested this in FF... – Romain Linsolas Jan 28 '09 at 14:04
  • This would be the best answer here if it was correct. Correct one of your lines to this: `var visibleBottom = visibleTop + window.innerHeight;` I'm not using jQuery and you helped me find the correct answer. – Bitterblue Oct 29 '13 at 14:22
3

Modified the accepted answer so that the element has to have it's display property set to something other than "none" to quality as visible.

function isScrolledIntoView(elem) {
   var docViewTop = $(window).scrollTop();
  var docViewBottom = docViewTop + $(window).height();

  var elemTop = $(elem).offset().top;
  var elemBottom = elemTop + $(elem).height();
  var elemDisplayNotNone = $(elem).css("display") !== "none";

  return ((elemBottom <= docViewBottom) && (elemTop >= docViewTop) && elemDisplayNotNone);
}
evanmcd
  • 1,901
  • 2
  • 30
  • 50
3

Here is a way to achieve the same thing using Mootools, in horizontal, vertical or both.

Element.implement({
inVerticalView: function (full) {
    if (typeOf(full) === "null") {
        full = true;
    }

    if (this.getStyle('display') === 'none') {
        return false;
    }

    // Window Size and Scroll
    var windowScroll = window.getScroll();
    var windowSize = window.getSize();
    // Element Size and Scroll
    var elementPosition = this.getPosition();
    var elementSize = this.getSize();

    // Calculation Variables
    var docViewTop = windowScroll.y;
    var docViewBottom = docViewTop + windowSize.y;
    var elemTop = elementPosition.y;
    var elemBottom = elemTop + elementSize.y;

    if (full) {
        return ((elemBottom >= docViewTop) && (elemTop <= docViewBottom)
            && (elemBottom <= docViewBottom) && (elemTop >= docViewTop) );
    } else {
        return ((elemBottom <= docViewBottom) && (elemTop >= docViewTop));
    }
},
inHorizontalView: function(full) {
    if (typeOf(full) === "null") {
        full = true;
    }

    if (this.getStyle('display') === 'none') {
        return false;
    }

    // Window Size and Scroll
    var windowScroll = window.getScroll();
    var windowSize = window.getSize();
    // Element Size and Scroll
    var elementPosition = this.getPosition();
    var elementSize = this.getSize();

    // Calculation Variables
    var docViewLeft = windowScroll.x;
    var docViewRight = docViewLeft + windowSize.x;
    var elemLeft = elementPosition.x;
    var elemRight = elemLeft + elementSize.x;

    if (full) {
        return ((elemRight >= docViewLeft) && (elemLeft <= docViewRight)
            && (elemRight <= docViewRight) && (elemLeft >= docViewLeft) );
    } else {
        return ((elemRight <= docViewRight) && (elemLeft >= docViewLeft));
    }
},
inView: function(full) {
    return this.inHorizontalView(full) && this.inVerticalView(full);
}});
bmlkc
  • 317
  • 3
  • 8
3

This method will return true if any part of the element is visible on the page. It worked better in my case and may help someone else.

function isOnScreen(element) {
  var elementOffsetTop = element.offset().top;
  var elementHeight = element.height();

  var screenScrollTop = $(window).scrollTop();
  var screenHeight = $(window).height();

  var scrollIsAboveElement = elementOffsetTop + elementHeight - screenScrollTop >= 0;
  var elementIsVisibleOnScreen = screenScrollTop + screenHeight - elementOffsetTop >= 0;

  return scrollIsAboveElement && elementIsVisibleOnScreen;
}
3

I prefer using jQuery expr

jQuery.extend(jQuery.expr[':'], {  
    inview: function (elem) {
        var t = $(elem);
        var offset = t.offset();
        var win = $(window); 
        var winST = win.scrollTop();
        var elHeight = t.outerHeight(true);

        if ( offset.top > winST - elHeight && offset.top < winST + elHeight + win.height()) {
            return true;    
        }    
        return false;  
    }
});

so you can use it this way

$(".my-elem:inview"); //returns only element that is in view
$(".my-elem").is(":inview"); //check if element is in view
$(".my-elem:inview").length; //check how many elements are in view

You can easly add such code inside scroll event function etc. to check it everytime user will scroll the view.

Adam Pietrasiak
  • 10,495
  • 6
  • 66
  • 82
  • The use of `expr` for checking an element's scroll visibility has a huge disadvantage since it may only work for elements which are not inside scroll containers themselves, because you are using the `window` object as the hard-coded scope. – vsync Feb 06 '18 at 12:21
3

An example based off of this answer to check if an element is 75% visible (i.e. less than 25% of it is off of the screen).

function isScrolledIntoView(el) {
  // check for 75% visible
  var percentVisible = 0.75;
  var elemTop = el.getBoundingClientRect().top;
  var elemBottom = el.getBoundingClientRect().bottom;
  var elemHeight = el.getBoundingClientRect().height;
  var overhang = elemHeight * (1 - percentVisible);

  var isVisible = (elemTop >= -overhang) && (elemBottom <= window.innerHeight + overhang);
  return isVisible;
}
Community
  • 1
  • 1
Brendan Nee
  • 4,276
  • 1
  • 27
  • 32
3

There are over 30 answers to this question, and none of them use the amazingly simple, pure JS solution that I have been using. There is no need to load jQuery just to solve this, as many others are pushing.

In order to tell if the element is within the viewport, we must first determine the elements position within the body. We do not need to do this recursively as I once thought. Instead, we can use element.getBoundingClientRect().

pos = elem.getBoundingClientRect().top - document.body.getBoundingClientRect().top;

This value is the Y difference between the top of the object and the top of the body.

We then must tell if the element is within view. Most implementations ask if the full element is within the viewport, so this is what we shall cover.

First of all, the top position of the window is: window.scrollY.

We can get the bottom position of the window by adding the window's height to its top position:

var window_bottom_position = window.scrollY + window.innerHeight;

Lets create a simple function for getting the element's top position:

function getElementWindowTop(elem){
    return elem && typeof elem.getBoundingClientRect === 'function' ? elem.getBoundingClientRect().top - document.body.getBoundingClientRect().top : 0;
}

This function will return the element's top position within the window or it will return 0 if you pass it something other than an element with the .getBoundingClientRect() method. This method has been around for a long time, so you shouldn't have to worry about your browser not supporting it.

Now, our element's top position is:

var element_top_position = getElementWindowTop(element);

And or element's bottom position is:

var element_bottom_position = element_top_position + element.clientHeight;

Now we can determine if the element is within the viewport by checking if the element's bottom position is lower than the viewport's top position and by checking if the element's top position is higher than the viewport's bottom position:

if(element_bottom_position >= window.scrollY 
&& element_top_position <= window_bottom_position){
    //element is in view
else
    //element is not in view

From there, you can perform the logic to add or remove an in-view class on your element, which you can then handle later with transition effects in your CSS.

I am absolutely amazed that I did not find this solution anywhere else, but I do believe that this is the cleanest and most effective solution, and it doesn't require you to load jQuery!

WebWanderer
  • 7,827
  • 3
  • 25
  • 42
  • Very nice explanation! But there are already answers that do exactly what you do, like [Ally's answer](http://stackoverflow.com/a/21627295/3107430) – Domysee Apr 05 '17 at 14:03
  • 1
    @Domysee Hmm, I somehow skipped over that. Fair enough. Thank you for pointing that out though. It is nice to see this done another way. – WebWanderer Apr 06 '17 at 04:21
3

The easiest solution I found for this is Intersection Observer API:

var observer = new IntersectionObserver(function(entries) {
    if(entries[0].isIntersecting === true)
        console.log('Element has just become visible in screen');
}, { threshold: [0] });

observer.observe(document.querySelector("#main-container"));
Manash Kumar
  • 652
  • 3
  • 9
2

Simple modification for scrollable div (container)

var isScrolledIntoView = function(elem, container) {
    var containerHeight = $(container).height();
    var elemTop = $(elem).position().top;
    var elemBottom = elemTop + $(elem).height();
    return (elemBottom > 0 && elemTop < containerHeight);
}

NOTE: this does not work if the element is larger than the scrollable div.

2

I adapted this short jQuery function extension, which you can feel free to use (MIT licence).

/**
 * returns true if an element is visible, with decent performance
 * @param [scope] scope of the render-window instance; default: window
 * @returns {boolean}
 */
jQuery.fn.isOnScreen = function(scope){
    var element = this;
    if(!element){
        return;
    }
    var target = $(element);
    if(target.is(':visible') == false){
        return false;
    }
    scope = $(scope || window);
    var top = scope.scrollTop();
    var bot = top + scope.height();
    var elTop = target.offset().top;
    var elBot = elTop + target.height();

    return ((elBot <= bot) && (elTop >= top));
};
Lorenz Lo Sauer
  • 20,692
  • 12
  • 75
  • 85
2

I have written a component for the task, designed to handle large numbers of elements extremely fast (to the tune of <10ms for 1000 elements on a slow mobile).

It works with every type of scroll container you have access to – window, HTML elements, embedded iframe, spawned child window – and is very flexible in what it detects (full or partial visibility, border box or content box, custom tolerance zone, etc).

A huge, mostly auto-generated test suite ensures that it works as advertised, cross-browser.

Give it a shot if you like: jQuery.isInView. Otherwise, you might find inspiration in the source code, e.g. here.

hashchange
  • 5,971
  • 1
  • 40
  • 40
2

I was looking for a way to see if the element is going to come into view soon, so by extending the snippets above i managed to do it. thought i would leave this here just in case it will help someone

elm = is the element you want to check is in the view

scrollElement = you can pass window or a parent element that has a scroll

offset = if you want it to fire when the element is 200px away before its in the screen then pass 200

function isScrolledIntoView(elem, scrollElement, offset)
        {
            var $elem = $(elem);
            var $window = $(scrollElement);
            var docViewTop = $window.scrollTop();
            var docViewBottom = docViewTop + $window.height();
            var elemTop = $elem.offset().top;
            var elemBottom = elemTop + $elem.height();
            
            return (((elemBottom+offset) >= docViewBottom) && ((elemTop-offset) <= docViewTop)) || (((elemBottom-offset) <= docViewBottom) && ((elemTop+offset) >= docViewTop));
        }
2

We can do something like this in modern browsers using ES6:

const isFullySeen = el => el &&
  typeof el.getBoundingClientRect === 'function' &&
  el.getBoundingClientRect()['bottom'] + window.scrollY <= 
    window.innerHeight + window.scrollY && 
  el.getBoundingClientRect()['top'] + window.scrollY <= 
    window.innerHeight + window.scrollY;
Alireza
  • 83,698
  • 19
  • 241
  • 152
2

I added my code modification. Unfortunately, I can see everyone in their version and everyone omits the use of the debance function. Which answer is for your Event not to fire, for example, 200 times per second while scrolling.

$(window).scroll(function(){
        if (isInView($('.class'))){
            debounce(
                someFunction(), 5
            )
        }
    });
    function isInView(elem){
        if(document.documentElement.clientWidth > 991){
            return $(elem).offset().top - $(window).scrollTop() < $(elem).height();
        }else {
            doSometing;
        }
    }
1

I just wanted to share that I combined this with my script to move the div so that it always stays in view:

    $("#accordion").on('click', '.subLink', function(){
        var url = $(this).attr('src');
        updateFrame(url);
        scrollIntoView();
    });

    $(window).scroll(function(){
            changePos();
    });

  function scrollIntoView()
  {
        var docViewTop = $(window).scrollTop();
        var docViewBottom = docViewTop + $(window).height();    
        var elemTop = $("#divPos").offset().top;
        var elemBottom = elemTop + $("#divPos").height();               
        if (elemTop < docViewTop){
            $("#divPos").offset({top:docViewTop});
        }
   }

   function changePos(){        
    var scrTop = $(window).scrollTop();
    var frmHeight = $("#divPos").height()
        if ((scrTop < 200) || (frmHeight > 800)){   
         $("#divPos").attr("style","position:absolute;");
        }else{
          $("#divPos").attr("style","position:fixed;top:5px;");
        }
    }
tree
  • 688
  • 3
  • 12
  • 22
0

The only plugin which works consistently for me for doing this, is: https://github.com/customd/jquery-visible

I ported this plugin to GWT recently since I didn't want to add jquery as a dependency just for using the plugin. Here's my (simple) port (just including the functionality that I need for my use case):

public static boolean isVisible(Element e)
{
    //vp = viewPort, b = bottom, l = left, t = top, r = right
    int vpWidth   = Window.getClientWidth();
    int vpHeight = Window.getClientHeight();


    boolean tViz = ( e.getAbsoluteTop() >= 0 && e.getAbsoluteTop()<  vpHeight);
    boolean bViz = (e.getAbsoluteBottom() >  0 && e.getAbsoluteBottom() <= vpHeight);
    boolean lViz = (e.getAbsoluteLeft() >= 0 && e.getAbsoluteLeft() < vpWidth);
    boolean rViz = (e.getAbsoluteRight()  >  0 && e.getAbsoluteRight()  <= vpWidth);

    boolean vVisible   = tViz && bViz;
    boolean hVisible   = lViz && rViz;

    return hVisible && vVisible;
}
Click Upvote
  • 235,452
  • 251
  • 553
  • 736
0

Checks if the element is on screen at all, rather than the accepted answer's approach that checks if the div is entirely on the screen (which won't work if div is bigger than the screen). In pure Javascript:

/**
 * Checks if element is on the screen (Y axis only), returning true
 * even if the element is only partially on screen.
 *
 * @param element
 * @returns {boolean}
 */
function isOnScreenY(element) {
    var screen_top_position = window.scrollY;
    var screen_bottom_position = screen_top_position + window.innerHeight;

    var element_top_position = element.offsetTop;
    var element_bottom_position = element_top_position + element.offsetHeight;

    return (inRange(element_top_position, screen_top_position, screen_bottom_position)
    || inRange(element_bottom_position, screen_top_position, screen_bottom_position));
}

/**
 * Checks if x is in range (in-between) the
 * value of a and b (in that order). Also returns true
 * if equal to either value.
 *
 * @param x
 * @param a
 * @param b
 * @returns {boolean}
 */
function inRange(x, a, b) {
    return (x >= a && x <= b);
}
Will Squire
  • 4,504
  • 2
  • 29
  • 45
0

Made a simple plugin detecting if element is visible within a scrollable container

    $.fn.isVisible = function(){

      var win;
      if(!arguments[0])
      {
        console.error('Specify a target;');
        return false;
      }
      else
      {
        win = $(arguments[0]);
      }
      var viewport = {};
      var bounds = this.offset();
      bounds.right = bounds.left + this.outerWidth();
      bounds.bottom = bounds.top + this.outerHeight();
      viewport.bottom = win.height() + win.offset().top;
      return (!( bounds.top > viewport.bottom) && (win.offset().top < bounds.bottom));
    };

Call it like this $('elem_to_check').isVisible('scrollable_container');

Hope it'll help.

user3491125
  • 299
  • 3
  • 12
0

The jquery scrollspy plugin will allow you to easily do this. https://github.com/thesmart/jquery-scrollspy

$('.tile').on('scrollSpy:enter', function() {
    console.log('enter:', $(this).attr('id'));
});

$('.tile').on('scrollSpy:exit', function() {
    console.log('exit:', $(this).attr('id'));
});

$('.tile').scrollSpy();
desbest
  • 4,269
  • 11
  • 45
  • 76
0

The only solution that worked for me is (return true when $("#elementToCheck") is visible):

$(document).scrollTop()+window.innerHeight+$("#elementToCheck").height()>$("#elementToCheck").offset().top

nonozor
  • 761
  • 1
  • 12
  • 23
0

On Typescript

  private readonly isElementInViewPort = (el: HTMLElement): boolean => {
      const rect = el.getBoundingClientRect();
      const elementTop = rect.top;
      const elementBottom = rect.bottom;
      const scrollPosition = el?.scrollTop || document.body.scrollTop;
      return (
        elementBottom >= 0 &&
        elementTop <= document.documentElement.clientHeight &&
        elementTop + rect.height > elementTop &&
        elementTop <= elementBottom &&
        elementTop >= scrollPosition
      );

};

Jorciney
  • 418
  • 5
  • 7
-1

Javascript only :)

function isInViewport(element) {
  var rect = element.getBoundingClientRect();
  var html = document.documentElement;
  return (
    rect.top >= 0 &&
    rect.left >= 0 &&
    rect.bottom <= (window.innerHeight || html.clientHeight) &&
    rect.right <= (window.innerWidth || html.clientWidth)
  );
}
Bryan
  • 15,409
  • 22
  • 92
  • 119
-1

After running around unproductively to and using several codes that didn't work. This is what worked for me on vertical scroll visibility using Jquery. Replce '#footerplace' with the element you'd like to track vertically.

jQuery.expr.filters.offscreen = function(el) {
  var rect = el.getBoundingClientRect();
  console.log(rect)
  console.log('window height', $(window).height());

  return (
           (rect.top <= $(window).height()) && (rect.bottom <= $(window).height())
         );
};
$(document).scroll(function(){
    if ($('#footerplace').is(':offscreen')){
      console.log('this is true');
    $('#footerplace').is(':offscreen');
    } else {
     console.log('this is false');
    $('#footerplace').is(':offscreen');

    }
Damilola Boiyelove
  • 723
  • 1
  • 5
  • 14