かもメモ

自分の落ちた落とし穴に何度も落ちる人のメモ帳

GSAP ScrollTrigger で CSS アニメーションを発火させ、アニメーション終了のコールバックを使いたい

いわゆるガチャガチャ動かしたいという要望のホームページ制作(非 React)で初めて GSAP というアニメーションライブラリを使ってる。 できることがありすぎて、まだ理解しきれてないが今回は ScrollTrigger を使って CSS のクラスを変更しアニメーションを発火させた後に、アニメーション終了を持ってコールバックを実行したかったのメモ

ライブラリ選定のメモ

パララックス・アニメーションライブラリで比較をした

aos vs gsap vs scrollmagic vs t-scroll vs velocityjs vs waypoints vs wowjs | npm trends

多くのライブラリが DT であまりメンテされておらず、ほぼ唯一 GSAP が TypeScript 化されており比較的メンテナンスがされていたので選定した

JavaScriptIntersection information があるので、ある程度のことはこれで実装可能だし、昨今の Web アプリケーションではガチャガチャ動くのはあまり意味をなさない装飾なのでこの分野の需要が減少しているのかも知れない。といっても広告的な "ほうむぺーじ" ではまだまだ必要とされる分野だとも思う (jQueryだってめちゃくちゃ使われてるし)

ScrollTrigger を使ってクラス名を変更し CSS アニメーションを設定する

シンプルな CSS アニメーションを作成する

.js-fadeUp {
  opacity: 0;
  transform: translateY(20px);
  transition: all 0.6s ease-in-out;
}
.js-fadeUp.fade-in {
  opacity: 1;
  transform: translateY(0);
}

.js-fadeup クラスがある要素をターゲットに、.fade-in クラスが付けられるとアニメーションが開始される想定

GSAP でクラス名を変更する

import gsap from 'gsap';
import { ScrollTrigger } from 'gsap/ScrollTrigger';

// registerPlugin しないとエラーになる
gsap.registerPlugin(ScrollTrigger);

const targets = document.querySelectorAll('.js-fadeUp');
targets.forEach((target, index) => {
  ScrollTrigger.create({
    trigger: target,
    id: `fadeUpContent-${index + 1}`,
    start: 'top-=100 center',
    end: 'bottom top',
    toggleClass: { targets: target, className: 'fade-in' },
    once: true,
  });
});

これで .js-fadeup クラスのある要素がスクロールに応じてフェードインするようになった!

📝 note.

once: Boolean - If true, the ScrollTrigger will kill() itself as soon as the end position is reached once. This causes it to stop listening for scroll events and it becomes eligible for garbage collection. This will only call onEnter a maximum of one time as well. It does not kill the associated animation. It's perfect for times when you only want an animation to play once when scrolling forward and never get reset or replayed. It also sets the toggleActions to "play none none none".
cf. ScrollTrigger | GSAP | Docs & Learning

once を付けておくと一度だけ発火し、kill() されメモリを開放してくれるトノコト。
こってりとしたアニメーションを何度も見せられるのは、あまりユーザー体験が良いとは思わないので once を設定することにした。

CSS アニメーションが完了したらコールバックで対象要素を操作したい

アニメーション完了時に CSS のクラス名を変更するなど、アニメーション完了のコールバックで要素を操作したい
ScrollTrigger: onComplete callback - GSAP - GreenSock このスレッドには refresh イベントリスナーを使えば良いと回答されていたがうまく動作せず、scrollEnd を使ってみたが、アニメーションが発火した要素ごとにイベントが発火するものでは無さそうだった。 cf. static-addEventListener | GSAP | Docs & Learning

onEnter, onLeave コールバックを使えば、対象ごとにコールバックを設定できる

onEnter: Function - A callback for when the scroll position moves forward past the "start" (typically when the trigger is scrolled into view). It receives one parameter onLeave: Function - A callback for when the scroll position moves forward past the "end" (typically when the trigger is scrolled out of view). It receives one parameter
cf. static-addEventListener | GSAP | Docs & Learning

CSS アニメーションの時間を測って即コールバックをするなら onEnter を使いコールバック内で setTimeoutCSS アニメーションの時間分待てば良さそう 厳密さがあまり必要でないなら、onLeave を使う方が無駄にタイマーを使わずに済むので効率的だと思う

import gsap from 'gsap';
import { ScrollTrigger } from 'gsap/ScrollTrigger';

gsap.registerPlugin(ScrollTrigger);

const targets = document.querySelectorAll('.js-fadeUp');
targets.forEach((target, index) => {
  ScrollTrigger.create({
    trigger: target,
    id: `fadeUpContent-${index + 1}`,
    start: 'top-=100 center',
    end: 'bottom top',
    toggleClass: { targets: target, className: 'fade-in' },
    once: true,
    // css アニメーションを待って実行させる場合は onEnter を使う
    onEnter: (self) => {
      // self は globalThis.ScrollTrigger 型
      const target = self.vars.trigger; // gsap.DOMTarget | undefined
      // ⚠ HTMLElement 型として扱わないと target.classList が type error になる
      if (!target || target instanceof HTMLElement !== true) {
        return;
      }
      // css animation が終わるまで待つ
      window.setTimeout(() => {
        target.classList.add('fade-in-complete');
      }, 600);
    },
    // css アニメーションを特に考慮しないなら onLeave で十分
    onLeave: (self) => {
      const target = self.vars.trigger;
      if (!target || target instanceof HTMLElement !== true) {
        return;
      }
      target.classList.add('leaved');
    }
  });
});

ポイントとしては self.vars.trigger で取得できる、アニメーションのターゲット要素が gsap.DOMTarget | undefined 型なので classList などを使いたい場合は target instanceof HTMLElement で type guard してあげる必要がある部分

Sample

See the Pen GSAP CSS animation callback by KIKIKI (@kikiki_kiki) on CodePen.

onLeave はスクロールの高さが足りないと発火しない場合がある

なんとなくな理解だけど、GSAP の ScrollTrigger を使って CSS アニメーション発火した後にコールバックで処理ができるようになった!

おわり


[参考]