Search code examples
javascriptanimationgsapintersection-observer

GSAP and IntersectionObserver: Text .from opacity:0 to 1 flashing before animation


I am using GSAP and IntersectionObserver to animate every character of every h1 on scroll.

Everything seems to be working but the opacity part of the animation doesn't work as expected. Basically one can see the h1 before it goes to opacity:0 and then back to 1 (it reminds me of the infamous Flash Of Unstyled Text). I am using the .from method. I would like every h1 to be invisible before the animation but I can't figure out what I am doing wrong. Please check the snippet.

const titles = document.querySelectorAll("h1");
    const options = {
      root: null,
      threshold: 0.25,
      rootMargin: "-200px"
    };
    const observer = new IntersectionObserver(function(entries, observer) {
      entries.forEach(entry => {
        if (!entry.isIntersecting) {
          return;
        }
        entry.target.classList.add("anim-text");
        // TEXT SPLITTING
        const animTexts = document.querySelectorAll(".anim-text");
    
        animTexts.forEach(text => {
          const strText = text.textContent;
          const splitText = strText.split("");
          text.textContent = "";
    
          splitText.forEach(item => {
            text.innerHTML += "<span>" + item + "</span>";
          });
        });
        // END TEXT SPLITTING
    
        // TITLE ANIMATION
        const charTl = gsap.timeline();
    
        charTl.set("h1", { opacity: 1 }).from(
          ".anim-text span",
          {
            opacity: 0,
            x: 40,
            stagger: {
              amount: 1
            }
          },
          "+=0.5"
        );
        observer.unobserve(entry.target);
        // END TITLE ANIMATION
      });
    }, options);
    
    titles.forEach(title => {
      observer.observe(title);
    });
* {
  color: white;
  padding: 0;
  margin: 0;
}
.top {
  display: flex;
  justify-content: center;
  align-items: center;
  font-size: 2rem;
  height: 100vh;
  width: 100%;
  background-color: #279AF1;
}

h1 {
  opacity: 0;
  font-size: 4rem;
}

section {
  padding: 2em;
  height: 100vh;
}

.sec-1 {
  background-color: #EA526F;
}

.sec-2 {
  background-color: #23B5D3;
}

.sec-3 {
  background-color: #F9C80E;
}

.sec-4 {
  background-color: #662E9B;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.2.5/gsap.min.js"></script>
<div class="top">Scroll Down</div>
<section class="sec-1">
  <h1>FIRST</h1>
</section>
<section class="sec-2">
  <h1>SECOND</h1>
</section>
<section class="sec-3">
  <h1>THIRD</h1>
</section>
<section class="sec-4">
  <h1>FOURTH</h1>
</section>

Thanks a lot in advance for your help!


Solution

  • This is indeed a flash of unstyled content (FOUC) that occurs because the JavaScript waits to run until the page has loaded. GreenSock actually has a tutorial on removing FOUC that I recommend.

    The basic approach is to hide the elements using your CSS and modify your JS to work with the changed CSS (such as changing the .from() to a .to() or .fromTo()). You could do that by adding h1 { opacity: 0 } to your CSS and then add the following to the JS: gsap.set(h1, {opacity: 1});.

    Side note: GSAP has its own SplitText plugin that makes it easy to customize how the text is split (including by line), handles non-standard characters, and adds the ability to easily revert to the default. I highly recommend it if you're going to be splitting text up!