skip to Main Content

I would like to have div elements fade in and out when scrolling over them by changing the opacity. It sounds really simple but can’t get it to work.

The problem I have is that the div are in the middle of my page and not on top, so scrollTop shouldn’t work right?

var fade = $('.b_wrapper');
var range = 400;

$(window).on('scroll', function() {
  var st = $(this).scrollTop();
  fade.each(function() {
    var offset = $(this).offset().top;
    var height = $(this).outerHeight();
    offset = offset + height / 1;
    
    $(this).css({
      'opacity': 1 - (st + offset - range) / range
    });
  });
});
.content {
  height: 100px;
}

.b_wrapper {
  background: lightgreen;
  margin-top: 40px;
  padding: 20px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.1/jquery.min.js"></script>
<div class="content"></div>
<div class="b_wrapper">
  <p>this is a div</p>
</div>
<div class="b_wrapper">
  <p>this is a div</p>
</div>
<div class="b_wrapper">
  <p>this is a div</p>
</div>
<div class="b_wrapper">
  <p>this is a div</p>
</div>
<div class="b_wrapper">
  <p>this is a div</p>
</div>
<div class="b_wrapper">
  <p>this is a div</p>
</div>
<div class="b_wrapper">
  <p>this is a div</p>
</div>

(JsFiddle)

2

Answers


  1. You can use the Intersection Observer API. It watches for changes in the intersection between a target element and its ancestor element or the viewport.

    First, create a new observer with options:

    let options = {
      root: null,
      threshold: [0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1]
    }
    let observer = new IntersectionObserver(callback, options);
    
    • root specifies either the ancestor (containing) element, e.g. with document.querySelector(), or the viewport with null.

    • threshold can take an array of intersection thresholds to watch for, which we’ll directly use as opacity for our elements. So each time an element’s intersection ratio with the viewport reaches one of these values, the callback is triggered. (You can use a function here to build the array if you want a more fine-grained sequence and don’t want to write everything by hand.)

    Next, add each element to the observer:

    for (const target of document.querySelectorAll('.b_wrapper')) {
        observer.observe(target);
    }
    

    The callback is very simple, as we can directly use the provided intersectionRatio as opacity. entries is an array of all watched elements that you can loop through:

    let callback = (entries, observer) => {
      entries.forEach((entry) => {
        entry.target.style.opacity = entry.intersectionRatio
      });
    };
    

    So much to get you started. Go read the documentation, it’s excellent and has many examples.

    let callback = (entries, observer) => {
      entries.forEach((entry) => {
        entry.target.style.opacity = entry.intersectionRatio
      });
    };
    
    let options = {
      root: null,
      threshold: [0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1]
    }
    let observer = new IntersectionObserver(callback, options);
    
    for (const target of document.querySelectorAll('.b_wrapper')) {
        observer.observe(target);
    }
    .b_wrapper{
      margin: 20px;
      padding: 20px;
      background: lightgreen;
    }
    <div class="b_wrapper"><p>this is a div</p></div>
    <div class="b_wrapper"><p>this is a div</p></div>
    <div class="b_wrapper"><p>this is a div</p></div>
    <div class="b_wrapper"><p>this is a div</p></div>
    <div class="b_wrapper"><p>this is a div</p></div>
    <div class="b_wrapper"><p>this is a div</p></div>
    <div class="b_wrapper"><p>this is a div</p></div>
    <div class="b_wrapper"><p>this is a div</p></div>
    Login or Signup to reply.
  2. Probably not the best but here is my try :

    • On scroll, I check the position of every element to see wich one is the most centered

    • On window resize, I update the center of screen value

    let centerOfScreen = window.innerHeight / 2;
    let pageScroll;
    let closestCenteredDivPosition = 999999;
    let currentIndex;
    
    let wrappers = document.getElementsByClassName("wrapper");
    let positions = [];
    for (let e of wrappers) {
      positions.push(e.offsetTop + e.offsetHeight / 2);
    }
    
    highlightMostCenteredDiv = () => {
      pageScroll = document.documentElement.scrollTop || document.body.scrollTop;
      let currentValue;
      for (let i = 0; i < positions.length; i++) {
        currentValue = Math.abs(positions[i] - centerOfScreen - pageScroll);
        if (closestCenteredDivPosition > currentValue) {
          closestCenteredDivPosition = currentValue;
          currentIndex = i;
        }
      }
      if (!document.querySelector(".current")) {
        wrappers[currentIndex].classList.add("current");
      } else {
        if (wrappers[currentIndex] != document.querySelector(".current")) {
          document.querySelector(".current").classList.remove("current");
          wrappers[currentIndex].classList.add("current");
        }
      }
      closestCenteredDivPosition = 999999;
    }
    
    highlightMostCenteredDiv();
    
    document.addEventListener("scroll", highlightMostCenteredDiv)
    
    window.addEventListener("resize", () => {
      centerOfScreen = window.innerHeight / 2;
    })
    body {
      margin: 0;
    }
    
    .wrapper {
      background-color: orange;
      height: 100px;
      width: 100%;
      margin: 50px 0;
      opacity: 0.3;
      transition: opacity 1s ease;
    }
    
    .current {
      opacity: 1;
    }
    <div class="wrapper"></div>
    <div class="wrapper"></div>
    <div class="wrapper"></div>
    <div class="wrapper"></div>
    <div class="wrapper"></div>
    <div class="wrapper"></div>
    <div class="wrapper"></div>
    <div class="wrapper"></div>
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search