0

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!

Vito
  • 179
  • 1
  • 14

1 Answers1

0

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!

Zach Saucier
  • 24,871
  • 12
  • 85
  • 147
  • Thank you for your answer. Unfortunately the problem persists. I think the problem should be the fact that I am animating the span (=each character) while the whole `h1` remains untouched and therefore visible. But the `h1` needs to be visible before the span starts animating, right? I don't see the point adding an opacity animation on the whole `h1` in the same timeline. I guess I could hard-code every character of every `h1` in a span, but it seems very cumbersome to me. – Vito Mar 20 '20 at 14:31
  • @Vito Ah, sorry. I didn't look at your code carefully (this is why providing minimal demos is useful). The core approach is the same, just hide what you need to be hidden. I updated my answer. – Zach Saucier Mar 20 '20 at 14:34
  • 1
    Thank you for your help. Just one little correction, which made the whole thing working as I wanted: on the `.set` method I changed the selector to `entry.target` in order to get each `h1` that has beed scrolled to. I didn't know I could do this kind of selection. Thanks again! – Vito Mar 20 '20 at 23:21
  • Oh, sorry. Thought I did ^__^ – Vito Mar 22 '20 at 02:21