Search code examples
javascripthtmlscrolldom-eventsintersection-observer

IntersectionObserver method not working - Javascript


I have a div that I want to change color when it scrolls into the viewport, and I'm trying to achieve this with the new intersectionObserver method. I've set my parameters in the config callback, but I can't seem to get the observer itself to add the class to change the background color?

Any help would be amazing.

codepen: https://codepen.io/emilychews/pen/mXVBVK

const config = {
  root: null, 	// sets the framing element to the viewport
  rootMargin: '0px',
  threshold: 0.5
};

const box = document.getElementById('box');

let observer = new IntersectionObserver(function(entries) {
  observer.observe(box);
  entries.forEach(function(item){
    item.classList.add("active");
  });
}, config);
body {
  margin: 0; padding: 0;
  display:flex;
  justify-content: center;
  align-items: center;
  height: 300vh;
}

#box {
  width: 100px; 
  height: 100px;
  background: blue;}

.active {background: red;}
<div id="box"></div>


Solution

  • The function inside the IntersectionObserver constructor is called whenever an intersection changes. You can’t put observer.observe(box); inside it.

    Also, item isn’t a DOM element — it’s an IntersectionObserverEntry, so you can’t use .classList on it. You probably meant to address item.target.

    Even if the above is corrected, your CSS won’t change, because you’ve used the #box selector to set the background to blue, which has a higher specificity than .active. An easy fix is to change #box to .box and as HTML use <div id="box" class="box"></div> instead.

    The corrected code would then look like this:

    const config = {
        root: null, // Sets the framing element to the viewport
        rootMargin: "0px",
        threshold: 0.5
      },
      box = document.getElementById("box"),
      observer = new IntersectionObserver((entries) => entries
        .forEach(({ target: { classList } }) => classList.add("active")), config);
    
    observer.observe(box);
    body {
      margin: 0;
      padding: 0;
      display: flex;
      justify-content: center;
      align-items: center;
      height: 300vh;
    }
    .box {
      width: 100px;
      height: 100px;
      background: blue;
    }
    .active {
      background: red;
    }
    <div id="box" class="box"></div>

    Now you need some logic inside the callback:

    entries.forEach(({ target: { classList }, intersectionRatio }) => {
      if(intersectionRatio >= 0.5){
        classList.add("active");
      }
      else{
        classList.remove("active");
      }
    });
    

    This will make the <div> red when 50 % or more of it is visible. The observer will stop firing after 50 % or more is visible, so in some cases intersectionRatio will be exactly 0.5.

    const config = {
        root: null, // Sets the framing element to the viewport
        rootMargin: "0px",
        threshold: 0.5
      },
      box = document.getElementById("box"),
      observer = new IntersectionObserver((entries) => entries
        .forEach(({ target: { classList }, intersectionRatio }) => {
          if(intersectionRatio >= 0.5){
            classList.add("active");
          }
          else{
            classList.remove("active");
          }
        }), config);
    
    observer.observe(box);
    body {
      margin: 0;
      padding: 0;
      display: flex;
      justify-content: center;
      align-items: center;
      height: 300vh;
    }
    .box {
      width: 100px;
      height: 100px;
      background: blue;
    }
    .active {
      background: red;
    }
    <div id="box" class="box"></div>