Skip to main content

😵‍💫 Animating the caption

Step snapshot

We didn't even consider the caption yet, and for a good reason: it's tricky to guess when to start it's animation on the timeline we created above. You may find the correct start / end timing... until the client wants the scrolling part to last longer and crumbles your soul, as they do.

So, what's our take? Use yet another data-scroll-module-progress, which means creating another JS module! Sometimes, it's better to split things in multiple parts instead of trying to make it all work in one spot and losing some hair in the process 😌

Introducing FancyGalleryCaption!

The markup

views/partials/fancy-gallery.twig
<figcaption 
class="c-fancy-gallery_caption"
data-scroll
data-scroll-css-progress {# Optional, for debug #}
data-scroll-module-progress
{#
Custom scroll offset, we want the text to start revealing from the bottom third of the screen
and we want it to be fully revealed when the end of the section starts appearing at the bottom of the screen
#}
data-scroll-offset="33%,100%"
data-module-fancy-gallery-caption {# Call the new module #}
>
{% for word in gallery.caption | split(' ') %}
<span class="c-fancy-gallery_word">
<span
class="c-fancy-gallery_word-inner"
data-fancy-gallery-caption="word"
>{{word}}</span>
</span>
{% endfor %}
</figcaption>

The module

On this occasion, we don't need to handle any resize event since we're only using percentage values! We're safe to let CSS do the work 😌 Also, no pixels computing & no exceptions means we can use GSAP's stagger property 😌 Here we're only translating each word on y, taking advantage of the clip-path we prepared on the parent element to get a sweet de-masking effect!

assets/scripts/modules/FancyGalleryCaption.js
import gsap from 'gsap'
import { module } from 'modujs'

export default class extends module {
constructor(m) {
super(m);

// Element selectors
this.$words = Array.from(this.$('word'));
}

init() {
// No need to handle resize here as nothing is computed in pixels,
// we only use percentages so we let CSS do the work :)

// Start timeline stuff
this.computeTl();
}

computeTl() {
// Create the timeline
this.tl = gsap.timeline({});

// Animate each item with a stagger (delay)
this.tl.from(this.$words, {
y: '130%',
stagger: .25,
opacity: 0,
duration: 1,
ease: 'power2.out'
});

this.tl.progress(0); // Force progress to 0 on init
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
}

onScrollProgress(value) {
this.tl?.progress?.(value);
}

destroy() {
super.destroy();
this.tl?.kill?.();
}
}
info

You may notice we used .from() instead of .to() on this occasion! Sometimes it's easier to think in reverse with GSAP!