みなさん、JavaScriptでスクロール量(位置)に応じてなんらかのアニメーションを実行したいとき、どのように書きますか?

例えば、jQueryを使ってスクロールイベントによるアニメーションを作るには、以下のように書いたりするでしょう。ブラウザへの負荷対策でlodashのthrottledebounceを使ったりしてもいいでしょう。

$(window).on('scroll',function(){
    var scroll_top = $(window).scrollTop();
    $('.scroll_wrapper').each(function(){
      var offset_top = $(this).offset().top - $(this).height();
      if( scroll_top > offset_top){
          $(this).addClass('isShow');       
      }else{
          $(this).removeClass('isShow');       
      }
    });
});

そんななか、2020年5月にGSAP(GreenSock Animation Platform)から新たなスクロールアニメのためのライブラリ「ScrollTrigger」がリリースされました。
GSAPは、その他にTweenMaxMotionPathでも知られており、中にはそれらを使ったことがある人もいるかもしれません。

で、「どれどれ…」と試しに使ってみたのですが、、、これが、めちゃくちゃ使いやすい! あまりに簡単にスクロールアニメが実装できたのです。体感としてはほぼ「一瞬」。

というわけで、本記事では「ScrollTrigger」の概要の解説と、簡単な使い方の解説をします。

ScrollTriggerの特徴

ScrollTriggerの公式ドキュメントには、以下のように特徴が列記されていますので、一部、かいつまんで紹介します。

  • アニメーションを特定の要素にリンクさせられる(かつ、その要素がビューポート内にあるときだけ再生できる)
  • スクロールバーに直接リンクした動きも簡単に作れる
  • 特定のスクロール位置の間で要素を固定できる「ピン留め機能」
  • スクロール位置を定義する構文の書き方が柔軟
  • リッチなコールバックシステム
  • デバッグを容易にする視覚的なマーカー
  • パフォーマンス最適化

ざっとピックアップしただけでもこれだけの量です。

なかでもやはり、特筆すべきは「アニメーションを特定の要素のリンクさせられる」という部分でしょう。

例えば、.box という要素がビューポート内に入ってきたときになにかアニメーションさせたい場合、最もシンプルな書き方だと以下のように書くだけで動作します。もちろん、スクロールイベントを取得して〜、という処理は不要です。

gsap.to(".box", {
  scrollTrigger: ".box", // ".box"がビューポートに入ったらアニメーション開始 (一度だけ)
  x: 500
});

どうです? めちゃくちゃシンプルじゃないですか?

基本機能だけならこれだけで動くシンプルさが、ScrollTriggerの最大の特徴です。これまで長々と書いてたのはなんだったのか……。

ScrollTriggerのここが便利

実際に触ってみて、それらの機能のなかで特に「Web制作において、めちゃくちゃ便利!」と思った機能は大きく以下の2つ。

開始/終了位置が視覚的にわかる「マーカー機能」

インスタンスを作る際に、オプションで"markers: true"と指定するだけで、アニメーションが開始/終了する位置を自動的にマーキングしてくれます。これがとても便利なのです。

もしかしたら、これまでそうしたマーカーを自作していた方もいるかもしれませんが、そういった手間が一切省けます。

また、開始/終了位置を指定する構文もいろんな書き方のパターンがあって柔軟です(後述します)。「この位置で始めて、ここに来るまでには終わらせたい」といった細かな指定のための調整もカンタンです。

デフォルトでスクロールイベントの間引き処理が行われている

冒頭でも書いたように、スクロールイベントを取得してなにかアニメーションさせる場合、ブラウザへの負荷を考えてイベント量を間引くケースが少なくないでしょう。
参考:lodash の debounce や throttle で簡単に負荷対策

しかし、ScrollTriggerの特徴には以下のように記載されており、イベントを間引く処理もデフォルトで行われているとのこと。こうした手間が省けるのはとても楽で、ScrollTriggerを使う理由のひとつになるでしょう。

Highly optimized for maximum performance – scroll events are debounced, updates are synchronized with GSAP and screen refreshes, resize recalculations are throttled, etc.

Docs – GreenSock

ScrollTriggerの使い方

それでは使い方を見ていきましょう。

なお、今回は環境構築が簡単なNuxt.jsで試しました。基本的な使い方はどの書き方でも変わらないので、適宜読み替えてください。

ソースコードとデモサイトを用意したので、そちらもあわせて見てみてください。

ソース: https://gitlab.com/shimotsu/scrolltrigger
デモ: https://scrolltrigger.netlify.app/

インストール方法

基本のインストール方法はこちらを参照してください。

まずは、GSAPのモジュールをインストールし、

yarn add gsap

gsap、ScrollTriggerをそれぞれimportします。

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

その後、gsap.registerPlugin() メソッドでScrollTriggerを登録します。Nuxt.jsではクライアント側でのみ実行したいので、process.client がtrueの場合のみ登録するようにします。

if (process.client) {
  gsap.registerPlugin(ScrollTrigger)
}

また、Nuxt.jsの場合、依存関係を変換するためにnuxt.config.jsに設定を追記しておく必要があります。

export default {
  (中略)
  build: {
    transpile: ['gsap']
  }
}

これで準備完了です。

スクロールアニメを実装する

早速アニメーションを実装していきます。

今回は、①一度のみの動き②スクロール位置にリンクする動き③組み合わせによる複雑な動き、の3パターンを作ってみました。

なお、すべてアニメーションの開始/終了位置を分かりやすくするために、マーカーを表示しています。

1. 一度のみの動き

一度だけ、スクロールに応じたアニメーションをする場合です。

デモ: https://scrolltrigger.netlify.app/

<template>には黒い動く要素 .a を用意しています。細かいhtmlのコードはGitを参照ください。

scriptは、以下のように書いています。黒い要素を動かす処理を scrollItemA()というメソッドで定義し、ページコンポーネントのMount時に実行しています。

書き方はとてもシンプルなので、概ねパッと理解できるのではないでしょうか。

export default {
  mounted() {
    this.scrollItemA()
  },
  methods: {
    scrollItemA() {
      gsap.to('.a', { // 動かしたい要素は".a"
        x: 500, // 右方向に500動く
        duration: 1, // アニメーションは1秒間
        scrollTrigger: {
          trigger: '.a', // 要素".a"がビューポートに入ったときにアニメーション開始
          start: 'center center', // アニメーション開始位置
          markers: true // マーカー表示
        }
      })
    },
  }
}

ひとつ、ちょっと厄介なのが start: 'center center' という部分。これはアニメーションの開始位置を定義している記述で、構文は以下のようになっています。

つまりこの場合、「要素 .a の中心(縦方向)が、画面の中心にきたとき、一度だけアニメーションを実行せよ」ということになります。上のGIFを見て確認してもらえれば分かると思います。

今回は center と記述しましたが、

  • topbottomcenterleftrightといった文字列
  • +=300px-=50pxといった相対値
  • 20%80%といった画面内の位置

など、書き方のパターンはいくつかあります。

また、今回はトリガーとなる要素と、動かしたい要素が同じでしたが、必ずしも合わせる必要はありません。要素Aに差し掛かったとき、要素Bを動かす、という書き方ももちろんできます。

2. スクロール位置にリンクする動き

次は、スクロール位置にリンクする動きです。

デモ: https://scrolltrigger.netlify.app/

この動きは以下のように書いて動作しています。

export default {
  mounted() {
    this.scrollItemB()
  },
  methods: {
    scrollItemB() {
      gsap.to('.b', { // 動かしたい要素は".b"
        x: 600, // 右方向に600動く
        rotation: 360, // 開始〜終了までの間で360度回転する
        duration: 1, // アニメーションは1秒間
        scrollTrigger: {
          trigger: '.b', // 要素".b"がビューポートに入ったときにアニメーション開始
          start: 'top center', // アニメーション開始位置
          end: 'top 300px', // アニメーション終了位置
          scrub: true, // アニメーションをスクロール位置にリンクさせる
          markers: true // マーカー表示
        }
      })
    },
  }
}

1番目と違い、アニメーションが発火したら1度だけ動くのではなく、スクロール位置に応じてアニメーションの進捗がリンクしていることがわかります。

これは、16行目の scrub: trueというオプションで実現しています。(GreenSockの公式サイトでは、これを「スクラバーのような動き」と表現しています)

アニメーションの開始/終了位置を見てみましょう。

start: 'top center', // アニメーション開始位置
end: 'top 300px', // アニメーション終了位置

先程の構文と照らし合わせると、開始位置は「要素 .b の頂点(上の端)が、画面の中心にきたとき」で、終了位置は「要素 .bの頂点(要素の上端)が、(画面の上端から)300pxの位置にきたとき」となります。

この開始/終了位置の構文は最初ちょっとややこしかったですが、一度慣れるとすごく直感的に書けるようになるので、それまではいろいろな値を試してマーカーで確認するとよいかもしれません。

3. 組み合わせによる複雑な動き

最後に、いくつかの動きを組み合わせて複雑な動きを作ってみます。

デモ: https://scrolltrigger.netlify.app/multi/

このケースでは、.wrapper という灰色の要素をトリガーとし、黒い要素を動かしています。

export default {
  mounted() {
    this.scrollItemC()
  },
  methods: {
    scrollItemC() {
      const tl = gsap.timeline({
      scrollTrigger: {
        trigger: ".wrapper",  // トリガーとなる要素は".wrapper"

        // スクローラの中央の位置が起点(50%)
        // トリガー要素.wrpperの中央に来たら発火(center)
        start: "center 50%",

        // スクローラの頂点が上から25%の位置が起点(25%)
        // トリガー要素.wrpperの中央に来たら終了(center)
        end: "center 25%",
        scrub: true,
        markers: true
      }
    });

    // アニメーションの一連の動き
    // 基本、すべて動かす要素は ".a"
    tl.to(".a", {y: 200, scale: 2})
    tl.to(".a", {x: 200, scale: 3})
    tl.to(".a", {rotation: 360, y: 400, scale: 4})
    tl.to(".a", {rotation: 90, x: 1000, y: 800, scale: 5})
    }
  }
}

スクロールに関する設定の書き方はこれまでとあまり変わりませんが、今回の場合 gsap.timeline()というメソッドを使って、複数の動きを直列につなげています。

これを見て分かるとおり、アニメーションの開始/終了位置の関係をちゃんと理解しさえすれば、ある程度複雑な動きでもあとは組み合わせ次第で作れるようになります。そこからはアイデア勝負、という感じですね。

さいごに

GSAPのScrollTriggerの使い方と概要を簡単に説明してみました。

今回触れた部分は本当に基本的な内容で、snapやコールバックシステムなど、まだまだ紹介しきれていない奥深い機能がたくさんあります。興味のある方は、ぜひ公式サイトを見て試してみてください。

ScrollTrigger – Plugins – GreenSock