94

I need to retrieve the visible height of a div within a scrollable area. I consider myself pretty decent with jQuery, but this is completely throwing me off.

Let's say I've got a red div within a black wrapper:

In the graphic above, the jQuery function would return 248, the visible portion of the div.

Once the user scrolls past the top of the div, as in the above graphic, it would report 296.

Now, once the user has scrolled past the div, it would again report 248.

Obviously my numbers aren't going to be as consistent and clear as they are in this demo, or I'd just hard code for those numbers.

I have a bit of a theory:

  • Get the height of the window
  • Get the height of the div
  • Get the initial offset of the div from the top of the window
  • Get the offset as the user scrolls.
    • If the offset is positive, it means the top of the div is still visible.
    • if it's negative, the top of the div has been eclipsed by the window. At this point, the div could either be taking up the whole height of the window, or the bottom of the div could be showing
    • If the bottom of the div is showing, figure out the gap between it and the bottom of the window.

It seems pretty simple, but I just can't wrap my head around it. I'll take another crack tomorrow morning; I just figured some of you geniuses might be able to help.

Thanks!

UPDATE: I figured this out on my own, but looks like one of the answers below is more elegant, so I'll be using that instead. For the curious, here's what I came up with:

$(document).ready(function() {
    var windowHeight = $(window).height();
    var overviewHeight = $("#overview").height();
    var overviewStaticTop = $("#overview").offset().top;
    var overviewScrollTop = overviewStaticTop - $(window).scrollTop();
    var overviewStaticBottom = overviewStaticTop + $("#overview").height();
    var overviewScrollBottom = windowHeight - (overviewStaticBottom - $(window).scrollTop());
    var visibleArea;
    if ((overviewHeight + overviewScrollTop) < windowHeight) {
        // alert("bottom is showing!");
        visibleArea = windowHeight - overviewScrollBottom;
        // alert(visibleArea);
    } else {
        if (overviewScrollTop < 0) {
            // alert("is full height");
            visibleArea = windowHeight;
            // alert(visibleArea);
        } else {
            // alert("top is showing");
            visibleArea = windowHeight - overviewScrollTop;
            // alert(visibleArea);
        }
    }
});
JacobTheDev
  • 17,318
  • 25
  • 95
  • 158
  • I would look into vh units. 1vh = 1/100th of the viewport height. You will probably find a solution with that. Find the height of the viewport, the height of the element, the elements position, and the scroll position and calculate accordingly. – davidcondrey Jul 15 '14 at 21:55
  • Assuming there is margin on the inner DIV, say "10px" all around. I would detect the scroll height to see if it's passed "10", then I would just get the height of the parent element, subtract it based on it's scroll height. –  Jul 15 '14 at 21:59
  • If all else fails, I just came across this script which looks like it may serve the purpose you need: http://larsjung.de/fracs/ – davidcondrey Jul 15 '14 at 22:04

4 Answers4

66

Calculate the amount of px an element (height) is in viewport

Fiddle demo

This tiny function will return the amount of px an element is visible in the (vertical) Viewport:

function inViewport($el) {
    var elH = $el.outerHeight(),
        H   = $(window).height(),
        r   = $el[0].getBoundingClientRect(), t=r.top, b=r.bottom;
    return Math.max(0, t>0? Math.min(elH, H-t) : Math.min(b, H));
}

Use like:

$(window).on("scroll resize", function(){
  console.log( inViewport($('#elementID')) ); // n px in viewport
});

that's it.


jQuery .inViewport() Plugin

jsFiddle demo

from the above you can extract the logic and create a plugin like this one:

/**
 * inViewport jQuery plugin by Roko C.B.
 * http://stackoverflow.com/a/26831113/383904
 * Returns a callback function with an argument holding
 * the current amount of px an element is visible in viewport
 * (The min returned value is 0 (element outside of viewport)
 */
;(function($, win) {
  $.fn.inViewport = function(cb) {
     return this.each(function(i,el) {
       function visPx(){
         var elH = $(el).outerHeight(),
             H = $(win).height(),
             r = el.getBoundingClientRect(), t=r.top, b=r.bottom;
         return cb.call(el, Math.max(0, t>0? Math.min(elH, H-t) : Math.min(b, H)));  
       }
       visPx();
       $(win).on("resize scroll", visPx);
     });
  };
}(jQuery, window));

Use like:

$("selector").inViewport(function(px) {
  console.log( px ); // `px` represents the amount of visible height
  if(px > 0) {
    // do this if element enters the viewport // px > 0
  }else{
    // do that if element exits  the viewport // px = 0
  }
}); // Here you can chain other jQuery methods to your selector

your selectors will dynamically listen to window scroll and resize but also return the initial value on DOM ready trough the first callback function argument px.

Community
  • 1
  • 1
Roko C. Buljan
  • 196,159
  • 39
  • 305
  • 313
  • To work on Android Chrome you may need add `` into head html section. – userlond Apr 14 '16 at 05:36
  • @userlond thanks for your contribution, but I'm not sure `content="width=1259"` might suit for the majority. Have you tried `content="width=device-width, initial-scale=1"` instead? – Roko C. Buljan Apr 14 '16 at 05:38
  • Site has legacy template with fixed width and is not responsive... I think that your suggested will be ok for majority. So to clarify: if Roko C. Buljan's solution doesn't work, try to add `` declaration :) – userlond Apr 14 '16 at 06:10
  • This is so awesome and so close to what I have been searching for. How can I calculate the distance from the elements bottom to the bottom of the window? So I can load more content before you see the end of the content. For example, if elBottom < 200 from windowBot, fire AJAX. – Thom Jul 07 '17 at 00:28
  • 1
    @Thom well, the above method does not return any other specific values (it can be expanded to return more stuff than just `visible height px`, such as an object. See this fiddle for such an idea: https://jsfiddle.net/RokoCB/6xjq5gyy/ (open developer console to see the logs) – Roko C. Buljan Jul 07 '17 at 05:36
  • @RokoC.Buljan Thanks for the help. I have this working so as you scroll down the page, the pages get loaded. Works fine. I now have the problem of what happens when you come into the middle of the page and need to load previous pages. I tried to reverse the whole thing, but since the pages are loaded above, the top space gets bigger and bigger and all the top pages get loaded at once, not as you scroll and they are needed. Any thoughts on how to accomplish the loading of previous pages as you scroll up? – Thom Jul 10 '17 at 15:27
  • instead of `(b – Olivier Boissé May 08 '18 at 22:35
  • getBoundingClientRect doesnt exist for me – Andrew Mar 25 '19 at 11:53
  • @Andrew it exists, it exists: https://caniuse.com/#feat=getboundingclientrect You just have to learn how to use it. Or at least see the errors in console and start by fixing those. – Roko C. Buljan Mar 25 '19 at 13:07
  • I thought it couldn't be that hard. jQuery must have something that fits, but appranetly not. This looked like just what I needed, however I get, `Uncaught TypeError: $(...).inViewport is not a function` – djack109 Jun 26 '20 at 10:52
60

Here is a quick and dirty concept. It basically compares the offset().top of the element to the top of the window, and the offset().top + height() to the bottom of the window:

function getVisible() {
  var $el = $('#foo'),
    scrollTop = $(this).scrollTop(),
    scrollBot = scrollTop + $(this).height(),
    elTop = $el.offset().top,
    elBottom = elTop + $el.outerHeight(),
    visibleTop = elTop < scrollTop ? scrollTop : elTop,
    visibleBottom = elBottom > scrollBot ? scrollBot : elBottom;
  $('#notification').text(`Visible height of div: ${visibleBottom - visibleTop}px`);
}

$(window).on('scroll resize', getVisible).trigger('scroll');
html,
body {
  margin: 100px 0;
}

#foo {
  height: 1000px;
  background-color: #C00;
  width: 200px;
  margin: 0 auto;
}

#notification {
  position: fixed;
  top: 0;
  left: 0;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
<div id="foo"></div>
<div id="notification"></div>

The logic can be made more succinct if necessary, I've just declared separate variables for this example to make the calculation as clear as I can.

Rory McCrossan
  • 331,213
  • 40
  • 305
  • 339
  • I'm having trouble setting this for multiple `div`s without just repeating the code. Is there a way to do this? – JacobTheDev Jul 16 '14 at 14:22
  • 2
    @Rev here you go: http://jsfiddle.net/b5DGj/1/. I separated each element to it's own function call. You could even go further and define a plugin to do this for you. – Rory McCrossan Jul 16 '14 at 14:34
  • I've slightly rewritten @RoryMcCrossan's approach to function as a jQuery plugin and posted it as an answer below - it may have more general applicability in that format. – Michael.Lumley Apr 29 '15 at 13:13
  • Sometimes an element can be partially hidden because of overflow or parent elements, regardless of the "window" – Nathan B Apr 12 '19 at 08:58
11

Here is a version of Rory's approach above, except written to function as a jQuery plugin. It may have more general applicability in that format. Great answer, Rory - thanks!

$.fn.visibleHeight = function() {
    var elBottom, elTop, scrollBot, scrollTop, visibleBottom, visibleTop;
    scrollTop = $(window).scrollTop();
    scrollBot = scrollTop + $(window).height();
    elTop = this.offset().top;
    elBottom = elTop + this.outerHeight();
    visibleTop = elTop < scrollTop ? scrollTop : elTop;
    visibleBottom = elBottom > scrollBot ? scrollBot : elBottom;
    return visibleBottom - visibleTop
}

Can be called with the following:

$("#myDiv").visibleHeight();

jsFiddle

Michael.Lumley
  • 2,345
  • 2
  • 31
  • 53
  • 2
    Don't work in case when window becomes bigger and block can fit. Thats why we need to reset block height: $(this).css('height', ''); http://jsfiddle.net/b5DGj/144/ – Max Koshel Dec 02 '15 at 09:42
0

Here is the improved code for jquery function visibleHeight: $("#myDiv").visibleHeight();

$.fn.visibleHeight = function() {
    var elBottom, elTop, scrollBot, scrollTop, visibleBottom, visibleTop, height;
    scrollTop = $(window).scrollTop();
    scrollBot = scrollTop + $(window).height();
    elTop = this.offset().top;
    elBottom = elTop + this.outerHeight();
    visibleTop = elTop < scrollTop ? scrollTop : elTop;
    visibleBottom = elBottom > scrollBot ? scrollBot : elBottom;
    height = visibleBottom - visibleTop;
    return height > 0 ? height : 0;
}
  • 2
    Your answer could be improved with additional supporting information. Please [edit] to add further details, such as citations or documentation, so that others can confirm that your answer is correct. You can find more information on how to write good answers [in the help center](/help/how-to-answer). – Community Jun 03 '22 at 13:17