1

Say you have a web page with multiple large panels, you can easily calculate what percentage of the entire page the user has scrolled to with the following code.

$(window).scroll(function(){

    var windowTop = $(this).scrollTop()
    var bodyHeight = $('body').height()

    var percentage = (windowTop/bodyHeight) * 100
    console.log(percentage)

});

However what I want to do is calculate, for one individual panel, what percentage of that panel has been scrolled into the viewport. So this will be 0% consistently until you get to the panel, and once you've scrolled to the bottom of that panel it'll be 100%.

I feel like this might be a simple equation but I just can't wrap my brain around what I need to do to achieve this.

MyNotes
  • 426
  • 4
  • 11
  • You can use `.getBoundingClientRect()` [jQuery trigger when 2/3s of div are in viewport](https://stackoverflow.com/questions/29140800/jquery-trigger-when-2-3s-of-div-are-in-viewport/) or `IntersectionObserver()` [How to know scroll to element is done in Javascript?](https://stackoverflow.com/questions/46795955/how-to-know-scroll-to-element-is-done-in-javascript/) – guest271314 Oct 20 '17 at 14:24
  • If you want this is exactly what you want. https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API It also have a polyfill – Akxe Oct 20 '17 at 15:55
  • I've added an answer which shows how to do this, feel free to give it a try. – JstnPwll Oct 20 '17 at 18:37

4 Answers4

5

Here's a constructor which can watch given element(s) and return a data object about the percentage on screen. It watches for elements both above and below the viewport.

var OnScreenPercentage = function(querySelector, callback){
  var self = this;
  this.callback = callback;
  this.elements = querySelector?$(querySelector):[];

  this.remove = function(){
    if(this.handler){
      $(window).off("scroll", this.handler);
    }
  }
  var calcTop = function(rect, win){
    return Math.max(rect.height+rect.y, 0)/rect.height;
  }
  var calcBottom = function(rect, win){
    return Math.max(win.height-rect.y, 0)/rect.height;
  }
  var calcPercentages = function(){
      var win = {height: $(window).height()}; 
      for(var e=0; e<self.elements.length; ++e){
        var rect = self.elements[e].getBoundingClientRect();
        self.callback({
          element: self.elements[e],
          percentage: rect.y<0 ? calcTop(rect, win) : (rect.y+rect.height)>win.height ? calcBottom(rect, win) : 1,
          location: rect.y<0 ? 'top' : (rect.y+rect.height)>win.height ? 'bottom' : 'middle'
        });
      }
  }
  this.handler = $(window).scroll(calcPercentages);
  calcPercentages();
}

//Example usage
var onScreen = new OnScreenPercentage('.box', function(data){
  var percent =  $(data.element).find('.percentage');
  $(percent).width(100*data.percentage+'%');
  if(data.location=='top'||data.location=='middle'){
    $(percent).css({left:'0', right: ''});
  }else{
    $(percent).css({left:'', right:'0'});
  }
});
.box {
  height: 100px;
  line-height: 100px;
  background-color: rgba(0,0,0,0.1);
  margin: 5px;
  position: relative;
  color: #fff;
  font-size: 30px;
  text-align: center;
}
.percentage {
  background: #00F;
  position: absolute;
  top: 0;
  bottom: 0;
  z-index: -1;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>

<div class="box">Box 0 <div class="percentage"></div></div>
<div class="box">Box 1 <div class="percentage"></div></div>
<div class="box">Box 2 <div class="percentage"></div></div>
<div class="box">Box 3 <div class="percentage"></div></div>
<div class="box">Box 4 <div class="percentage"></div></div>
<div class="box">Box 5 <div class="percentage"></div></div>
<div class="box">Box 6 <div class="percentage"></div></div>
<div class="box">Box 7 <div class="percentage"></div></div>
<div class="box">Box 8 <div class="percentage"></div></div>
JstnPwll
  • 8,585
  • 2
  • 33
  • 56
2

If I understand correctly, from your description it sounds like you are looking to see when the element enters the viewport.

That means you need to calculate the position of the bottom of the window (so that you can compare it to the position of the top of the element). You can calculate the position of the bottom of the window like so:

var windowBottom = $(this).scrollTop() + $(this).height();

Then you need to know at what position the element enters the viewport. You can get the top position of the element like so:

var elementTop = $(".element").offset().top;

You can then calculate how much of the element has been scrolled into the viewport by subtracting the element's top position from the position of the bottom of the window. You divide this number by the element's height to get the percentage like so:

var percentage = (windowBottom - elementTop) / $(".element").height() * 100;

Try the snippet below or check out this CodePen Demo:

$(document).ready(function() {

  $(window).scroll(function() {
    var windowBottom = $(this).scrollTop() + $(this).height();
    var elementTop = $(".element").offset().top;
    var percentage = (windowBottom - elementTop) / $(".element").height() * 100;

    if (percentage >= 100) {
      $(".percent").text('100%');
    } else if (windowBottom >= elementTop) {
      $(".percent").text(`${Math.round(percentage)}%`);
    } else {
      $(".percent").text('0%');
    }
  });

});
body {
  margin: 0;
  padding: 0;
  color: white;
}

.flex-layout {
  display: flex;
  flex-direction: column;
  justify-content: space-between;
}

.info-container {
  position: fixed;
  z-index: 1;
  font-size: 2em;
  height: 100%;
  width: 100%;
  color: white;
}

.block {
  height: 800px;
  background: #333;
}

.element {
  align-items: center;
  background: #000;
  height: 550px;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>

<div class='info-container flex-layout'>
  <span>Scroll down</span>
  <span class='percent'>0%</span>
</div>


<div class='block'></div>
<div class='element flex-layout'>
  <span>Start</span>
  <span>End</span>
</div>
<div class='block'></div>

I hope this helps and is what you're looking for.

Dan Kreiger
  • 5,358
  • 2
  • 23
  • 27
2

Looks like on stackoverflow it's possible to find only jQuery solutions. And I don't like jQuery. So here it is:

**
 * @param {HTMLElement} element
 * @returns {number} percent of element in view
 */
function getPercentOfView(element) {
    const viewTop = window.pageYOffset;
    const viewBottom = viewTop + window.innerHeight;
    const rect = element.getBoundingClientRect();
    const elementTop = rect.top + viewTop;
    const elementBottom = elementTop + rect.height;

    if (elementTop >= viewBottom || elementBottom <= viewTop) {
        // heigher or lower than viewport
        return 0;
    } else if (elementTop <= viewTop && elementBottom >= viewBottom) { 
        // element is completely in viewport and bigger than viewport
        return 100;
    } else if (elementBottom <= viewBottom) {
        if (elementTop < viewTop) {
            // intersects viewport top
            return Math.round((elementBottom - viewTop) / window.innerHeight * 100);
        } else {
            // completely inside viewport
            return Math.round((elementBottom - elementTop) / window.innerHeight * 100);;
        }
    } else {
        // intersects viewport bottom
        //  elementBottom >= viewBottom && elementTop <= viewBottom
        return Math.round((viewBottom - elementTop) / window.innerHeight * 100);
    }
}

cuddlemeister
  • 1,586
  • 12
  • 15
0

my version, as the above didn't work.

getScrollPercentage(el){
  const percentage = (window.scrollY - el.offsetTop + el.scrollHeight) / el.scrollHeight;
  return percentage < 0 ? 0 : Math.min(percentage, 1)
}
Tadas Majeris
  • 361
  • 2
  • 4
  • 12