Search code examples
javascriptjqueryscrollviewport

jQuery - calculate the scroll ratio of an element when it enters the veiwport?


How can I calculate the scroll ratio of an element as soon as it enters the viewpoint?

My combination of code from a couple of sources:

$(document).ready(function(){

  var initY = $('.scrollratio').offset().top
  var height = $('.scrollratio').height()
  var endY  = initY + $('.scrollratio').height()

  $(window).scroll(function(){
    var scroll = $(window).scrollTop()

    var visible = isVisible(document.getElementById("demo"))
    console.log(visible)

    if (visible) {
      console.log('in view')
    } else {
      console.log('out of view')
    }

    if(visible){
      var diff = scroll - initY
      var ratio = Math.round((diff / height) * 100)
      $('#note').text(ratio)
    }
  })
})

// Check if the element is in the viewport.
// http://www.hnldesign.nl/work/code/check-if-element-is-visible/
function isVisible(node) {
    // Am I visible?
    // Height and Width are not explicitly necessary in visibility detection, the bottom, right, top and left are the
    // essential checks. If an image is 0x0, it is technically not visible, so it should not be marked as such.
    // That is why either width or height have to be > 0.
    var rect = node.getBoundingClientRect()
    return (
        (rect.height > 0 || rect.width > 0) &&
        rect.bottom >= 0 &&
        rect.right >= 0 &&
        rect.top <= (window.innerHeight || document.documentElement.clientHeight) &&
        rect.left <= (window.innerWidth || document.documentElement.clientWidth)
    )
}
* {
    padding: 0;
    margin: 0;
}
body, html {
  height: 100%;
  margin: 0;
  font: 400 15px/1.8 "Lato", sans-serif;
  color: #777;
}

body {
  background:#171717;
}

.section-1 {
  width: 100%;
  min-height: 100%;
}

.scrollratio {
  height: 2000px;
  background:red;
}

#note {
  position: fixed;
  top: 0;
  left: 0;
  color: #FFFFFF;
  margin: 2em;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>

<div id="note" ></div>
<div class="section-1"></div>
<br><br><br><br><br><br><br><br><br><br><br>
<br><br><br><br><br><br><br><br><br><br><br>
<div class="scrollratio" id="demo"></div>
<br><br><br><br><br><br><br><br><br><br><br>
<br><br><br><br><br><br><br><br><br><br><br>
<br><br><br><br><br><br><br><br><br><br><br>
<br><br><br><br><br><br><br><br><br><br><br>

It gets negative values when the element (the red box) enters the viewport, and only gets the positive values when the element's top starts passing the window's top.

Any ideas how to get the positive values as soon as the element is in the viewport?

EDIT:

$(document).ready(function(){

  var initY = $('.scrollratio').offset().top
  var height = $('.scrollratio').height()
  var endY  = initY + $('.scrollratio').height()
  var wHeight = $(window).height();

  $(window).scroll(function(){
    var scroll = $(window).scrollTop()

    var visible = isVisible(document.getElementById("demo"))

    if(visible){
      var diff = scroll + wHeight - initY
      var ratio = Math.round((diff / height) * 100)
      $('#note').text(ratio)
    }
  })
})

// Check if the element is in the viewport.
// http://www.hnldesign.nl/work/code/check-if-element-is-visible/
function isVisible(node) {
    // Am I visible?
    // Height and Width are not explicitly necessary in visibility detection, the bottom, right, top and left are the
    // essential checks. If an image is 0x0, it is technically not visible, so it should not be marked as such.
    // That is why either width or height have to be > 0.
    var rect = node.getBoundingClientRect()
    return (
        (rect.height > 0 || rect.width > 0) &&
        rect.bottom >= 0 &&
        rect.right >= 0 &&
        rect.top <= (window.innerHeight || document.documentElement.clientHeight) &&
        rect.left <= (window.innerWidth || document.documentElement.clientWidth)
    )
}
* {
    padding: 0;
    margin: 0;
}
body, html {
  height: 100%;
  margin: 0;
  font: 400 15px/1.8 "Lato", sans-serif;
  color: #777;
}

body {
  background:#171717;
}

.section-1 {
  width: 100%;
  min-height: 100%;
}

.scrollratio {
  height: 2000px;
  background:red;
}

#note {
  position: fixed;
  top: 0;
  left: 0;
  color: #FFFFFF;
  margin: 2em;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>


<div id="note" ></div>
<div class="scrollratio" id="demo"></div>
<br><br><br><br><br><br><br><br><br><br><br>
<br><br><br><br><br><br><br><br><br><br><br>
<br><br><br><br><br><br><br><br><br><br><br>
<br><br><br><br><br><br><br><br><br><br><br>


Solution

  • I guess it's simply because you haven't accounted for the height of the window as well. Just add var wHeight = $(window).height(); to your calculations and I believe it's solved. See below. Also note that I've added the wheight variable outside the scroll event as it's unnecessary to call it each time - however, you might want to account for window resizes and update the variable in such case.
    PS: I've added the wHeight variable in the calculation of var diff =

    EDIT: as requested I've updated the javascript below, setting the wHeight conditionally based on whether it's within the "first" window height or not.

        $(document).ready(function(){
    
      var initY = $('.scrollratio').offset().top
      var height = $('.scrollratio').height()
      var endY  = initY + $('.scrollratio').height();
    
      if(initY > $(window).height()){
      wHeight = $(window).height();
      } else {
      wHeight = 0;
      };
    
      $(window).scroll(function(){
        var scroll = $(window).scrollTop()
        var visible = isVisible(document.getElementById("demo"));
    
        if(visible){
          var diff = scroll + wHeight - initY
          var ratio = Math.round((diff / height) * 100);
          $('#note').text(ratio)
        }
      })
    })
    
    // Check if the element is in the viewport.
    // http://www.hnldesign.nl/work/code/check-if-element-is-visible/
    function isVisible(node) {
        // Am I visible?
        // Height and Width are not explicitly necessary in visibility detection, the bottom, right, top and left are the
        // essential checks. If an image is 0x0, it is technically not visible, so it should not be marked as such.
        // That is why either width or height have to be > 0.
        var rect = node.getBoundingClientRect()
        return (
            (rect.height > 0 || rect.width > 0) &&
            rect.bottom >= 0 &&
            rect.right >= 0 &&
            rect.top <= (window.innerHeight || document.documentElement.clientHeight) &&
            rect.left <= (window.innerWidth || document.documentElement.clientWidth)
        )
    }
    * {
        padding: 0;
        margin: 0;
    }
    body, html {
      height: 100%;
      margin: 0;
      font: 400 15px/1.8 "Lato", sans-serif;
      color: #777;
    }
    
    body {
      background:#171717;
    }
    
    .section-1 {
      width: 100%;
      min-height: 100%;
    }
    
    .scrollratio {
      height: 2000px;
      background:red;
    }
    
    #note {
      position: fixed;
      top: 0;
      left: 0;
      color: #FFFFFF;
      margin: 2em;
    }
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
    
    
    <div id="note" ></div>
    <div class="section-1"></div>
    <br><br><br><br><br><br><br><br><br><br><br>
    <br><br><br><br><br><br><br><br><br><br><br>
    <div class="scrollratio" id="demo"></div>
    <br><br><br><br><br><br><br><br><br><br><br>
    <br><br><br><br><br><br><br><br><br><br><br>
    <br><br><br><br><br><br><br><br><br><br><br>
    <br><br><br><br><br><br><br><br><br><br><br>

    In case you would like to make the script "resize-proof" as well as avoid the negative numbers in case the initY < $(window).height(), you can do as follows (relies on global variables):

    $(document).ready(function(){
     function setVars(){
      initY = $('.scrollratio').offset().top
      height = $('.scrollratio').height()
      endY  = initY + $('.scrollratio').height();
    
      if(initY > $(window).height()){
      wHeight = $(window).height();
      } else {
      wHeight = 0;
      };
    };
    setVars();
      $(window).scroll(function(){
        var scroll = $(window).scrollTop()
        var visible = isVisible(document.getElementById("demo"));
    
        if(visible){
          var diff = scroll + wHeight - initY;
          var ratio = Math.round((diff / height) * 100);
          if(ratio >= 0){
            $('#note').text(ratio);
          };
        }
      })
     $(window).on("resize", setVars);
    })
    
    // Check if the element is in the viewport.
    // http://www.hnldesign.nl/work/code/check-if-element-is-visible/
    function isVisible(node) {
        // Am I visible?
        // Height and Width are not explicitly necessary in visibility detection, the bottom, right, top and left are the
        // essential checks. If an image is 0x0, it is technically not visible, so it should not be marked as such.
        // That is why either width or height have to be > 0.
        var rect = node.getBoundingClientRect()
        return (
            (rect.height > 0 || rect.width > 0) &&
            rect.bottom >= 0 &&
            rect.right >= 0 &&
            rect.top <= (window.innerHeight || document.documentElement.clientHeight) &&
            rect.left <= (window.innerWidth || document.documentElement.clientWidth)
        )
    }