【GSAP】Modifiersで作る無限ループするカルーセルデモについて解説

※本ブログの目的は個人の備忘録であり、コードは参考用として掲載しています。
実際に使用される際は、ご自身の環境で十分に動作確認を行ってください。
コードの利用によって生じたいかなる問題についても責任を負いかねますので、あらかじめご了承ください。

はじめに

要素を無限ループさせる方法には、CSSやJavaScriptなど様々な手法があります。

例えば「背景を流し続ける」「ロゴを横スクロールさせる」「バナーを自動でスライドさせる」などに使えます。

今回は、GSAPの「ModifiersPlugin」 を使って無限ループアニメーションを実現する方法を紹介します。

デモ(参考 CodePen)

今回は、GSAP公式ドキュメントのModifiersのページに記載されているこちらのカルーセルデモを解説していきます。

GSAP ModifiersPlugin: seamless loop

10個のボックスが横に流れ続けるシンプルなデモです。

見た目はずっと動いていますが、実際には 「x座標を一定範囲に保つ」 ことでループを再現しています。

仕組みを分解して解説

それではコードを分解して解説していきます。全体のコードは先程のcodepenのリンクからご確認ください。

仕組み

.boxを横一列に並べ、全体をラッパー(.wrapper)より少し長く(.box1個分)動かしています。

一番右のボックスが画面外へ抜けた瞬間に、左端へ位置を戻すことで、切れ目のない無限ループのように見せています。

ポイント及び前提条件

<div class="wrapper">
  <div class="boxes">
    <div class="box">1</div>
    <div class="box">2</div>
    <div class="box">3</div>
    <div class="box">4</div>
    <div class="box">5</div>
    <div class="box">6</div>
    <div class="box">7</div>
    <div class="box">8</div>
    <div class="box">9</div>
    <div class="box">10</div>
  </div>
</div>
  • 50pxの.boxが10個、絶対配置で設置しgsap.setで横並びに配置
  • .boxを内包する.wrapperは450pxで、.box1つ分小さい
  • .boxesleft:-50px.box1つ分左にズラす(最初の.boxを隠す)

以下で詳しくコードを見ていきましょう。

初期配置

// 初期配置:色と位置を設定
gsap.set(".box", {
  backgroundColor: (i) => colors[i % colors.length],
  x: (i) => i * 50
});

gsap.set.boxの初期状態を設定しています。ポイントはx: (i) => i * 50です。

10個のボックスを、50px間隔で横並びに配置しています。

i.boxのインデックス番号(0から始まる)を表します。

つまり、1個目は0となるので、x:0pxの位置、2個目はx:50pxの位置になります。

このとき、.boxを内包している.boxesにはleft:-50pxがかかっているため、全体が50px(.box1個分)ずつ左側に移動します。

つまり、1個目はx:0pxに設定していますが、それを全体として左にズラすことで、.boxの1個目のxが.wrapperの左側にはみ出ます。

この状態では、.wrapperoverflow:hiddenがかかっているため、はみ出た1個目は表示されず、2〜10の.boxが横並びに表示されている状態になります。

これにより「1個分の余白」を作り、無限ループの切れ目が見えないようにしています。

アニメーション

// 右方向に500px動かし、終点で座標をリセットしてループさせる
gsap.to(".box", {
  duration: 5,
  ease: "none",
  x: "+=500", // 500px右へ移動
  modifiers: {
    // xの値を常に0〜500の範囲に保つ
    x: gsap.utils.unitize(x => parseFloat(x) % 500)
  },
  repeat: -1 // 無限ループ
});

5秒かけて、すべてのボックスを500px(=.box10個分)右方向へ移動させています。

そしてrepeat: -1 により永遠に繰り返します。

.wrapperは450pxなので、1個分はみ出して移動します。

この構造によって、「はみ出すタイミングで自然にループ」が起こります。

このはみ出た(表示されない)瞬間に、.boxをx:0pxの位置に戻すことで、無限ループしているように見せています。

  • .boxes 全体が 左に50px(1個分)ズレて配置 されているため、最初の .box は画面の左外に隠れる
  • アニメーションが進むと、右端のボックスが .wrapper の外に出て見えなくなる(10個分進むため)
  • その瞬間に modifiers によって x 座標が0にリセット され、右から出ていったボックスが 左端の外側(見えない位置)に再配置 される

このとき、.wrapperoverflow: hidden; が指定されているため、リセットされた瞬間は視覚的に見えません。

つまり、「見えない場所で位置を戻し、次のフレームで再び右へ流れてくる」という動きを高速で繰り返すことで、“切れ目のないループ” を再現しています。

実際、codepenのサンプルの「Show overflow」をチェックして動作を確認してもらうとわかりやすいです。

そして肝心の位置を0に戻す仕組みこそがmodifiersです。続けてmodifiersについて見ていきましょう。

Modifiersで「座標を再計算」

modifiers: {
  x: gsap.utils.unitize(x => parseFloat(x) % 500)
}

ここが最重要ポイントです。

modifiersはアニメーション中、各フレームごとに xの値を一度取得 → 再計算 → 適用 しています。

modifyには修正するという意味があり、modifiersは取得した値を修正して適用する機能です。

% は「余剰演算子」と呼ばれ、割り算の余りを返します。

たとえば、520 % 50020 になります。これは「520 を 500 で割った余りが 20」という意味です。

この仕組みを利用して、要素が 500px 移動するたびに、位置が 0 にリセットされるようにしています。

つまり、x の値が 0〜499 の間ではそのまま進み続け、x が 500 になると余りが 0 になるため、再びスタート位置(0px)に戻ります。

たとえば、x の値が
0 → 100 → 200 … → 499 → 500 → 0 → 1 → 2 …
と進むイメージです。

まるで「500px 幅のトラックをぐるぐる走っている」ような動きになります。

0〜499までは500で割れないため、そのままの数値が余りとなり返されるので狙い通り499まで移動する

この繰り返しによって、要素が永遠にループしているように見える、というわけです。

gsap.utils.unitize() は、数値を "px" 付き文字列に戻してくれる便利関数です。

まとめ

  • GSAPのModifiersPlugin を使うと、アニメーション中の値を“動的に書き換える”ことができる
  • 剰余演算(%)を使えば、値を一定範囲で循環させられる

おわりに

.box が移動する瞬間が見えないようにするため、.boxes の初期位置をずらしたり、.wrapper の幅を .box 1個分だけ短くするなど、いくつかの工夫や制約があります。

そのため、一見やや複雑に見えるかもしれません。

しかし、この仕組みを応用すれば、.box のサイズが固定でなくても、無限にループするアニメーションを作ることが可能です。

ぜひ、実際に手を動かして挑戦してみてください。