MyNotes
MyNotes

Reputation: 445

Calculate what percentage of a specific element has been scrolled into view

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.

Upvotes: 2

Views: 5731

Answers (4)

Tadas Majeris
Tadas Majeris

Reputation: 371

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)
}

Upvotes: 0

cuddlemeister
cuddlemeister

Reputation: 1785

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);
    }
}

Upvotes: 2

Dan Kreiger
Dan Kreiger

Reputation: 5516

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.

Upvotes: 2

JstnPwll
JstnPwll

Reputation: 8685

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>

Upvotes: 6

Related Questions