Search code examples
javascriptcssoffsetfadein

Change the offset of a fade in when in-view javascript


I have a fade in function in js for a website, to be activated when the element is in the vertical viewport. Thing is, the element has to be completely in the viewport for the function to activate, and as I also set the element to move up while it fades in, you have to scroll a lot for it to work, which at times create a lot of whitespace that doesn't look good. Here's the js:

(function() {

  'use strict';

  // define variables
  var items = document.querySelectorAll(".fadeup");

  // check if an element is in viewport
  function isElementInViewport(el) {
    var rect = el.getBoundingClientRect();
    return (
      rect.top >= 0 &&
      rect.bottom <= (window.innerHeight || document.documentElement.clientHeight -50)
    );
  }


    function callbackFunc() {
      for (var i = 0; i < items.length; i++) {
        if (isElementInViewport(items[i])) {
          items[i].classList.add("in-view");
        }
      }
    }

    // listen for events
    window.addEventListener("load", callbackFunc);
    window.addEventListener("resize", callbackFunc);
    window.addEventListener("scroll", callbackFunc);

  })();

And the css:

.fadeup {
  visibility: hidden;
  opacity: 0;
  position: relative;
  top: 30px;
  transition: opacity 1s, top 1s, visibility 1s;
  }

.fadeup.in-view {
  top: 0px;
  transform: none;
  visibility: visible;
  opacity: 1;
}

How can I change the offset of the function, for it to be activated before the element is fully in the viewport? Maybe just halfway in, or a set amount of pixels in? I can't seem to find the way.

Thanks!


Solution

  • Your logic is almost there. You just modify the condition to check the bottom part of rect in isElementInViewport like below

    // check if an element is in viewport
    function isElementInViewport(el) {
        var rect = el.getBoundingClientRect();
        return (
          rect.top >= 0 &&
          //calculate from the top + a half of element
          rect.top + (rect.height/2) <= (window.innerHeight || document.documentElement.clientHeight)
        );
      }
    

    Full testing code

    (function() {
    
      'use strict';
    
      // define variables
      var items = document.querySelectorAll(".fadeup");
    
      // check if an element is in viewport
      function isElementInViewport(el) {
        var rect = el.getBoundingClientRect();
        return (
          rect.top >= 0 &&
          rect.top + (rect.height/2) <= (window.innerHeight || document.documentElement.clientHeight)
        );
      }
    
    
        function callbackFunc() {
          for (var i = 0; i < items.length; i++) {
            if (isElementInViewport(items[i])) {
              items[i].classList.add("in-view");
            }
          }
        }
    
        // listen for events
        window.addEventListener("load", callbackFunc);
        window.addEventListener("resize", callbackFunc);
        window.addEventListener("scroll", callbackFunc);
    
      })();
    .fadeup {
      visibility: hidden;
      opacity: 0;
      position: relative;
      top: 30px;
      transition: opacity 1s, top 1s, visibility 1s;
      height: 300px;
      border: 1px solid #ccc;
      }
    
    .fadeup.in-view {
      top: 0px;
      transform: none;
      visibility: visible;
      opacity: 1;
    }
    <div class="fadeup">
      Content
    </div>
    <div class="fadeup">
      Content
    </div>
    <div class="fadeup">
      Content
    </div>
    <div class="fadeup">
      Content
    </div>
    <div class="fadeup">
      Content
    </div>
    <div class="fadeup">
      Content
    </div>
    <div class="fadeup">
      Content
    </div>
    <div class="fadeup">
      Content
    </div>
    <div class="fadeup">
      Content
    </div>
    <div class="fadeup">
      Content
    </div>
    <div class="fadeup">
      Content
    </div>
    <div class="fadeup">
      Content
    </div>
    <div class="fadeup">
      Content
    </div>
    <div class="fadeup">
      Content
    </div>
    <div class="fadeup">
      Content
    </div>
    <div class="fadeup">
      Content
    </div>
    <div class="fadeup">
      Content
    </div>
    <div class="fadeup">
      Content
    </div>
    <div class="fadeup">
      Content
    </div>
    <div class="fadeup">
      Content
    </div>
    <div class="fadeup">
      Content
    </div>

    We also can have another effective way to control element visibility with IntersectionObserver

    Full example here

    (function() {
    
      'use strict';
    
      var items = document.querySelectorAll(".fadeup");
    
      let observer = new IntersectionObserver((entries, observer) => {
        entries.forEach(entry => {
          if (entry.isIntersecting) {
            entry.target.classList.add("in-view");
          }
    
        });
      }, {
        threshold: 0.5 //whenever the element is visible on a half of the screen
      });
      items.forEach(p => {
        observer.observe(p)
      });
    
    })();
    .fadeup {
      visibility: hidden;
      opacity: 0;
      position: relative;
      top: 30px;
      transition: opacity 1s, top 1s, visibility 1s;
      height: 300px;
      border: 1px solid #ccc;
    }
    
    .fadeup.in-view {
      top: 0px;
      transform: none;
      visibility: visible;
      opacity: 1;
    }
    <div class="fadeup">
      Content
    </div>
    <div class="fadeup">
      Content
    </div>
    <div class="fadeup">
      Content
    </div>
    <div class="fadeup">
      Content
    </div>
    <div class="fadeup">
      Content
    </div>
    <div class="fadeup">
      Content
    </div>
    <div class="fadeup">
      Content
    </div>
    <div class="fadeup">
      Content
    </div>
    <div class="fadeup">
      Content
    </div>
    <div class="fadeup">
      Content
    </div>
    <div class="fadeup">
      Content
    </div>
    <div class="fadeup">
      Content
    </div>
    <div class="fadeup">
      Content
    </div>
    <div class="fadeup">
      Content
    </div>
    <div class="fadeup">
      Content
    </div>
    <div class="fadeup">
      Content
    </div>
    <div class="fadeup">
      Content
    </div>
    <div class="fadeup">
      Content
    </div>
    <div class="fadeup">
      Content
    </div>
    <div class="fadeup">
      Content
    </div>
    <div class="fadeup">
      Content
    </div>

    BUT one thing I'd like to note down that IntersectionObserver does not fully support for all browsers (you can check browser compatibility here)