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:
- Open this fiddle
- Widen the output box to something wide (i.e.
1300px
) - Run the fiddle
- Scroll down and you'll see the elements not loading
- Resize the output box to something smaller
- The
scrollTrigger
animations now appear
Here is a gif showcasing the issue:
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