GSAP ScrollTrigger animations not working with Locomotive JS (until window is resized)

I have sections across my site which are using ScrollTrigger. Once I implemented Locomotive JS, all of my ScrollTrigger animations stopped working.

I read through the forums and saw that you need to update() ScrollTrigger when Locomotive is scrolling. I implemented this and saw no results.

Then, I resized the window and my ScrollTrigger animations that were in view, they started working.

In short, animations trigger on resize, but not on page load.

I've implemented update() and also tried refresh(), but no luck.

Demo (fiddle showing issue here also):

$(function() {

  gsap.registerPlugin(ScrollTrigger);

  function animateFrom(elem, direction) {
    direction = direction || 1;

    var x = 0, y = direction * 100;
    if(elem.classList.contains("gsap_reveal--fromLeft")) {
      x = -100;
      y = 0;
    } else if (elem.classList.contains("gsap_reveal--fromRight")) {
      x = 100;
      y = 0;
    }

    elem.style.transform = "translate(" + x + "px, " + y + "px)";
    elem.style.opacity = "0";

    gsap.fromTo(elem, { x: x, y: y, autoAlpha: 0 }, {
      duration: 2,
      x: 0,
      y: 0,
      autoAlpha: 1,
      ease: "expo",
      overwrite: "auto"
    });

  }

  function hide(elem) {
    gsap.set(elem, { autoAlpha: 0 });
  }

  gsap.utils.toArray(".gsap_reveal").forEach(function(elem) {
    hide(elem); // assure that the element is hidden when scrolled into view
    ScrollTrigger.create({
      trigger: elem,
      onEnter: function() { animateFrom(elem) },
      onEnterBack: function() { animateFrom(elem, -1) },
      onLeave: function() { hide(elem) } // assure that the element is hidden when scrolled into view
    });
  });

    const pageContainer = document.querySelector("[data-scroll-container]");

    const scroll = new LocomotiveScroll({
      el: pageContainer,
      smooth: true
    });


    // each time locomotive Scroll updates, tell ScrollTrigger to update too (sync positioning)
    scroll.on(pageContainer, ScrollTrigger.update);

    // tell ScrollTrigger to use these proxy methods for the ".data-scroll-container" element since Locomotive Scroll is hijacking things
    ScrollTrigger.scrollerProxy(pageContainer, {
      scrollTop(value) {
        return arguments.length ? scroll.scrollTo(value, 0, 0) : scroll.scroll.instance.scroll.y;
      }, // we don't have to define a scrollLeft because we're only scrolling vertically.
      getBoundingClientRect() {
        return {top: 0, left: 0, width: window.innerWidth, height: window.innerHeight};
      }
    });

    window.addEventListener("load", function (event) {
      ScrollTrigger.refresh();
    });

    ScrollTrigger.addEventListener("refresh", () => scroll.update());
    ScrollTrigger.refresh();


});
.hero {
  min-height: 1000px;
  background: lightblue;
  display: flex;
  align-items: center;
}

.textImageRepeater {
  overflow: hidden;
  padding: 120px 0 160px 0;
}
.textImageRepeater__intro {
  margin-bottom: 66px;
}
.textImageRepeater__layout--row {
  flex-direction: row !important;
}
.textImageRepeater__layout--rowReverse {
  flex-direction: row-reverse !important;
}
.textImageRepeater__item {
  display: flex;
  align-items: center;
  justify-content: center;
  flex-direction: column;
  padding-top: 70px;
  justify-content: space-between;
}
.textImageRepeater__header {
  margin: 17px 0;
}
.textImageRepeater__graphic {
  margin: 0;
}
.textImageRepeater__text, .textImageRepeater__graphic {
  flex-basis: 50%;
}
.textImageRepeater__text {
  max-width: 500px;
}

.c-scrollbar_thumb{
  background-color: #5D209F!important;
  width: 7px;
  margin: 2px;
  opacity: 1;
  border-radius: unset;
  mix-blend-mode: multiply;
}
<link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous">

<link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/locomotive-scroll.min.css" rel="stylesheet">

<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.min.js" integrity="sha512-894YE6QWD5I59HgZOGReFYm4dnWc1Qt5NtvYSaNcOP+u1T9qYdvdihz0PPSiiqn/+/3e7Jo4EaG7TubfWGUrMQ==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>

<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/locomotive-scroll.min.js"></script>

<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.9.1/gsap.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.9.1/ScrollTrigger.min.js"></script>

<div data-scroll-container>
  <div data-scroll-section>

    <section class="hero">
      <div class="container">
        <div class="row justify-content-center justify-content-xl-between">
          <div class="col-12 col-md-10 col-lg-9 col-xl-5">
            <div class="hero__text text-center text-xl-start">
              <h1 class="hero__title" data-scroll data-scroll-speed="2">Title</h1>
            </div>
          </div>
        </div>
      </div>
    </section>

    <section class="textImageRepeater">
      <div class="container">
        <div class="row">
          <div class="col-12">

            <div class="textImageRepeater__item textImageRepeater__layout--row">
              <div class="textImageRepeater__text text-center text-md-start gsap_reveal gsap_reveal--fromRight">
                <div class="textImageRepeater__copy">
                  <h2>Header</h2>
                  <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.</p>
                </div>
              </div>
              <div class="textImageRepeater__graphic text-center gsap_reveal gsap_reveal--fromLeft">
                <img class="textImageRepeater__image" src="https://picsum.photos/200/300" alt="placeholder-image" loading="lazy">
              </div>
            </div>

            <div class="textImageRepeater__item textImageRepeater__layout--rowReverse">
              <div class="textImageRepeater__text text-center text-md-start gsap_reveal gsap_reveal--fromRight">
                <div class="textImageRepeater__copy">
                  <h2>Header</h2>
                  <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.</p>
                </div>
              </div>
              <div class="textImageRepeater__graphic text-center gsap_reveal gsap_reveal--fromLeft">
                <img class="textImageRepeater__image" src="https://picsum.photos/200/300" alt="placeholder-image" loading="lazy">
              </div>
            </div>

          </div>
        </div>
      </div>
    </section>

  </div>
</div>

Test 1

What I've realised is, if I comment out all my locomotive js and run:

$(window).on('scroll', function() {
  console.log("test");
});

The logs will show, but they do not when locomotive is active.

Unsure if this may indicate what's going wrong?

Test 2

At the end of my snippet, I ran the following to check if refresh() was even running and it was returning the else statement. Again, not sure why.

if (ScrollTrigger.refresh()){
  console.log("true");
} else{
  console.log("false");
}

Steps to recreate:

  1. Open this fiddle
  2. Widen the output box to something wide (i.e. 1300px)
  3. Run the fiddle
  4. Scroll down and you'll see the elements not loading
  5. Resize the output box to something smaller
  6. The scrollTrigger animations now appear

Here is a gif showcasing the issue:

enter image description here


These are what I've tried to get the animation run.

1. Let ScrollTrigger know about the new scroller.

As you're using Locomotive scroll that means the native scroll which ScrollTrigger is based on is removed. So we bring it back by doing this:

ScrollTrigger.create({
    scroller: "[data-scroll-container]" // this is what you're missing
    // your other options
});

Read the docs for ScrollTrigger > scroller

2. Assign the ScrollTrigger tween at last.

I'm not really sure but the reason could be simply that the tweens need to be assigned once the whole scrolling system has been set.

// init Locomotive
// ...

// create ScrollTrigger
gsap.utils.toArray(".gsap_reveal").forEach(function(elem) {
    hide(elem); // assure that the element is hidden when scrolled into view
    ScrollTrigger.create({
      scroller: "[data-scroll-container]",
      trigger: elem,
      onEnter: function() { animateFrom(elem) },
      onEnterBack: function() { animateFrom(elem, -1) },
      onLeave: function() { hide(elem) } // assure that the element is hidden when scrolled into view
    });
  });

See JSFiddle