javascripthtmlcssgetboundingclientrect

Trouble with fading image into viewport through CSS/JS


I am attempting to make an image on a website fade in and up when a user scrolls the image into the viewport. The code I have so far is below. However, when I run this code I get a 404 error output. Any assistance is appreciated! I am quite new to JS and have been trying to figure this out for a while.

Here is my CSS.

.section3 {
        opacity: 0;
        transform: translateY(20vh);
        visibility: hidden;
        transition: opacity 0.6s ease-out, transform 1.2s ease-out;
        will-change: opacity, visibility;
    }
.fade {
        opacity: 1;
        transform: none;
        visibility: visible;
    }

Below is the HTML and JS.

<section id="section3" class="section3">
        <img style="width: 100%;" src="lovethyneighbor.jpg">
</section>

<script>
        var section3 = document.getElementById("section3");
        var location = section3.getBoundingClientRect();

        if (location.top >= 0) {
            document.getElementById("section3").classList.add("fade");
        } else {
            document.getElementById("section3").classList.add("section3");
        }
</script>

Solution

  • Introducing the Intersection Observer API! This is included in JavaScript and is a great tool for triggering an event/function when an element is in the viewport.

    It's a really powerful tool and I'd highly suggest using this over getBoundingClientRect(). One of the main reasons for this is with your code:

    if (location.top >= 0) {
         document.getElementById("section3").classList.add("fade");
    } 
    
    else {        
         document.getElementById("section3").classList.add("section3");
    }
    

    You will have to run a function on every single mousewheel event, which is unreliable and can hurt performance. If you're using Intersection Observer, the API will "watch" your page and will run a function whenever the element is in the viewport. The code below is explained through inline comments.

    Scalable with multiple elements that need different animations

    // the sections/containers
    const sections = document.querySelectorAll("section.section");
    
    // options for the intersection function
    const options = {
      root: null,
      threshold: 0.5, // how much of the element should be visible before the function is triggered? from 0 - 1
      rootMargin: "0px 0px 0px 0px" // default rootmargin value
    };
    
    // the observer - with foreach we can trigger multiple elements with multiple animations if need be
    let observer = new IntersectionObserver((entries) => {
      entries.forEach((entry) => {
    
        // the element that is going to be animated
        const block = entry.target.querySelector("img.fader");
    
        // elements to be animated, e.g.
        // if multiple elements with animations need to run inside the same section
        const animationBlocks = entry.target.querySelectorAll("[data-animation]");
    
        // when the element is triggered
        if (entry.isIntersecting) {
          // foreach, if multiple animations need to run on the same element
          animationBlocks.forEach((animation) => {
            animationClass = animation.dataset.animation;
    
            // adding the data-animation class class to the element, so the animation can run
            animation.classList.add(animationClass);
          });
        }
      });
    }, options);
    
    observer.observe(document.querySelector("section.section"));
    
    // running the animations
    document.addEventListener("DOMContentLoaded", function() {
      Array.from(sections).forEach(function(element) {
        observer.observe(element);
      });
    });
    body {
      height: 300vh;
      display: flex;
      align-items: center;
      justify-content: center;
      flex-direction: column;
      background-color: teal;
      gap: 400px;
    }
    
    /* starting values */
    [data-animation="fadeInUp"] {
      opacity: 0;
      transform: translate3d(0, 20px, 0);
    }
    
    /* when classname is applied to the element, run the animation */
    .fadeInUp {
      animation-name: fadeInUp;
      animation-duration: 0.6s;
      animation-fill-mode: both;
    }
    
    /* the animation */
    @keyframes fadeInUp {
      from {
        opacity: 0;
        transform: translate3d(0, 20px, 0);
      }
      to {
        opacity: 1;
        transform: translate3d(0, 0, 0);
      }
    }
    <section id="section2" class="section section2">
      <img data-animation="fadeInUp" class="fader" style="width: 100%;" src="https://picsum.photos/200/300">
    </section>
    
    <section id="section3" class="section section3">
      <img data-animation="fadeInUp" class="fader" style="width: 100%;" src="https://picsum.photos/200/300">
    </section>

    Simple function with single element and single animation

    const sections = document.querySelectorAll("section.section");
    
    const options = {
      root: null,
      threshold: 0.5
    };
    
    let observer = new IntersectionObserver((entries) => {
      entries.forEach((entry) => {
    
        const block = entry.target.querySelector("img.fader");
    
        if (entry.isIntersecting) {
          block.classList.add('fadeInUp');
        }
      });
    }, options);
    
    observer.observe(document.querySelector("section.section"));
    
    // running the animations
    document.addEventListener("DOMContentLoaded", function() {
      Array.from(sections).forEach(function(element) {
        observer.observe(element);
      });
    });
    body {
      height: 300vh;
      display: flex;
      align-items: center;
      justify-content: center;
      flex-direction: column;
      background-color: teal;
      gap: 400px;
    }
    
    img.fader {
      opacity: 0;
      transform: translate3d(0, 20px, 0);
    }
    
    .fadeInUp {
      animation-name: fadeInUp;
      animation-duration: 0.6s;
      animation-fill-mode: both;
    }
    
    
    /* the animation */
    
    @keyframes fadeInUp {
      from {
        opacity: 0;
        transform: translate3d(0, 20px, 0);
      }
      to {
        opacity: 1;
        transform: translate3d(0, 0, 0);
      }
    }
    <section id="section2" class="section section2">
      <img data-animation="fadeInUp" class="fader" style="width: 100%;" src="https://picsum.photos/200/300">
    </section>
    
    <section id="section3" class="section section3">
      <img class="fader" style="width: 100%;" src="https://picsum.photos/200/300">
    </section>