📈 GSAP Timeline setup
GSAP
You may not know about this library, but you should! Developped by GreenSock, its more than 10 years in the making and it was already a thing in ActionScript! It's a reference in the animation libraries and it's the one we use at Locomotive. Here's how they describe themselves:
Think of GSAP as the Swiss Army Knife of javascript animation...but better. It animates anything JavaScript can touch (CSS properties, canvas library objects, SVG, React, Vue, Angular, generic objects, whatever) and it solves countless browser inconsistencies, all with blazing speed (up to 20x faster than jQuery), including automatic GPU-acceleration of transforms. See the "Why GSAP?" article for details. Most other libraries only animate CSS properties. Plus, their sequencing abilities and runtime controls pale by comparison.
Simply put, GSAP is the most robust high-performance JavaScript animation library on the planet, which is probably why Google recommends it for JS-based animations and every major ad network excludes it from file size calculations. Unlike monolithic frameworks that dictate how you structure your apps, GSAP completely flexible; sprinkle it wherever you want.
In our case, their gsap.timeline({}) feature will be the key for our advanced animation.
Adding selectors
But first, we need to target some DOM elements and store them in order to be ready to apply them animations later. The elements are already identified in the twig file with this new snapshot:
{# ... #}
<div class="c-fancy-gallery_sticky-area">
<div class="c-fancy-gallery_sticky">
<div class="c-fancy-gallery_grid"
data-fancy-gallery="grid">
{% for image in gallery.images %}
<div class="c-fancy-gallery_grid-item"
data-fancy-gallery="grid-item">
{% include "@snippets/image.twig" with {
src: image.src,
width: image.width,
height: image.height,
attr: 'data-fancy-gallery="image"'
} only %}
</div>
{% endfor %}
</div>
</div>
</div>
{# ... #}
Let's add this snippet to our module's constructor in order to be able to acces them:
// Element Selectors
this.$grid = this.$('grid')[0]; // The grid itself
this.$gridItems = Array.from(this.$('grid-item')); // An array of all items on the grid
this.$images = Array.from(this.$('image')); // An array of the image elements themselves
We use this method to convert our selector containing multiple elements to an actual Array, giving us acces to for .. of and many useful Array features such as map(), filter(), find(), reduce() and more.
Add the timeline with a simple animation loop
Creating the timeline in itself is really easy, we only need to import the library and add one line of code: this.tl = gsap.timeline({}). For convenience, we'll put it inside a new function of our module: computeTl().
Let's try a simple animation first: with the following code, we will make the first grid item do a full rotation.
this.tl.to(this.$gridItems[0], { rotateZ: '360deg' })
Also, in order to see the animation better, let's make it loop by adding the repeat: -1 option to our timeline.
Here's everything in our module!
import gsap from 'gsap'
import { module } from 'modujs'
export default class extends module {
constructor(m) {
super(m);
// Element Selectors
this.$grid = this.$('grid')[0];
this.$gridItems = Array.from(this.$('grid-item'));
this.$images = Array.from(this.$('image'));
}
init() {
// Start timeline stuff
this.computeTl();
}
computeTl() {
// Create the timeline
this.tl = gsap.timeline({
repeat: -1 // Make it loop
});
// Rotate our first grid item
this.tl.to(this.$gridItems[0], { rotateZ: '360deg' })
}
// This method will be called automatically by `data-scroll-module-progress`
onScrollProgress(value) {
console.log(value)
}
destroy() {
super.destroy();
this.tl?.kill?.();
}
}
Sync it to the scroll
We've got an animation running, it's nice but not related to the scroll at all! For that to work, we need just a few tweaks:
- Disable the loop & progress autoplay
- Call
this.tl.progress(value)in ouronScrollProgress()method
//...
computeTl() {
// Create the timeline
this.tl = gsap.timeline({
repeat: -1 // Make it loop
});
// Rotate our first grid item
this.tl.to(this.$gridItems[0], { rotateZ: '360deg' })
this.tl.progress(0); // Set progress to 0
this.tl.pause(); // Very important to pause the timeline, we don't want it to play on its own: it's meant to be controlled by `onScrollProgress` only
}
// ...
// This method will be called automatically by `data-scroll-module-progress`
onScrollProgress(value) {
console.log(value)
// Update the timeline progress to match the scroll progress!
this.tl.progress(value)
}
Our animation now only plays on scroll inside the boundaries we defined earlier: success! 🎆
