こんにちは。フロントエンドエンジニアになりたてのぼこです。
この記事ではGSAPというアニメーションライブラリを使って、ページが途中から横にスクロールするような表現を実装してみたいと思います。
GSAPを知らない方にも読んでいただけるよう、導入方法から説明します。
技術系の記事を書くのは初めてなので至らない点が多いかとは思いますが、どなたかの助けになれば幸いです。
2022/1/28追記 画面リサイズに対応するためデモを修正、解説を追記しました。
目次
今回作るデモ
さっそくですが、今回紹介するデモがこちらです。
実際の操作はただ下へ下へスクロールしているだけですが、二つ目のセクションではスクロールに対して真横にページが動いているかと思います。
GSAPを使えばこんなことも簡単にできてしまうんですね。
ではでは解説に参ります。
GSAPってなに
まずはGSAPとはそもそも何なのかについてです(デモの解説だけ見たい方は飛ばしちゃってください)。
概要
GSAP(GreenSock Animation Platform)は、GreenSock社が開発している軽量で多機能、そして直感的に扱いやすい超スゴいJavaScriptアニメーションライブラリです。
GSAP(ジーサップ)と読むらしいです。
JavaScriptでアニメーションを制御しますが、JavaScriptの知識がなくても扱えてしまうくらい、簡単にアニメーションが作れます。
TweenMaxとの違い
GSAPは以前まで、その機能によってライブラリがいくつかに分かれていました。
TweenMaxなどはそのうちの一つで、現時点(2021年5月)での最新バージョンであるGSAP3から、それらが一つにまとめられて使いやすくなったという感じですね。
そのため、ざっくりいうとバージョンの違いで本質的には同じものを指していますが、書き方など変更点はいくつか存在しますので、参考にする際は注意しましょう。また、本記事で扱うデモはGSAP3で実装しています。
なぜGSAPを使うのか
GSAPの魅力はアニメーションを簡単に実装できてしまうことももちろんですが、他にも開発する上で嬉しいことがいっぱいあります。
パフォーマンスに優れている
今回扱うのはGSAPのScrollTriggerという機能ですが、ScrollTriggerでは「アニメーションの対象が画面内に入ってきたときだけ処理を実行する」といった最適化があらかじめ行われています。
どういうことかというと、jQueryなどを使った従来のスクロールアニメーションは、スクロールのたびに処理が毎回実行されていました。そうすると、アニメーションの内容によってはサイトの動作が重たくなってしまいます。
画面外の見えていない要素まで常に動かすのは無駄なので、画面から出たら処理を行わないなどの制御が求められるのです。しかしScrollTriggerはそのようなことを最初からやってくれており、使う側はアニメーションのデザインに集中できます。
デバッグが簡単
これもScrollTriggerの話になりますが、スクロールに紐付くアニメーションはタイミングの調整が難しかったりします。そんなときに使えるのが「マーカー機能」です。
アニメーションの開始と終了のタイミングを視覚的にわかりやすくするマーカーがデフォルトで備わっていて、オプション(後ほど紹介します)を指定するだけで表示できます。要素がどのタイミングでアニメーションし始めるかなどがとっても分かりやすくなるので、作業がしやすいです。
実際に使ってみる
それでは、実際にGSAPを使ってみましょう。
導入
CDNの場合
とりあえず手軽に始めたい場合はCDNが便利です。
HTMLファイル内のheadタグの中に以下の二つを追記しましょう。
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.6.1/gsap.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.6.1/ScrollTrigger.min.js"></script>
一つ目がGSAPのコアとなるファイルの読み込み、二つ目が今回使うScrollTriggerの読み込みです。これで準備が整いました。
*執筆時点(2021年5月)ではバージョンが3.6.1となっていますので注意してください。最新バージョンは公式サイトで確認できます。
npm / yarnの場合
npmやyarnを用いる場合はパッケージをインストールし、JavaScript内でimportします。
まずはお使いの環境に合わせてnpm i gsap もしくは yarn add gsapしてください。
そしてモジュールをimportします。
import gsap from 'gsap';
import { ScrollTrigger } from "gsap/ScrollTrigger";
また、npmやyarnでScrollTriggerなどの機能を扱う場合は、以下のコードを追記してプラグインの登録をする必要があります。
gsap.registerPlugin(ScrollTrigger);
詳細は公式サイトにあるのでこちらも見てみてください。
デモの解説
See the Pen
GSAP Side Scroll by Bokoko33 (@bokoko33)
on CodePen.
実際のソースがこちらになります(表示サイズの関係で少し見づらいので、実際にデモを触る場合は右上のEDIT ON CODEPENをクリックしてCodePen上で開いてください)。
HTML / CSS
HTMLやCSSについては重要な点のみ抜粋して解説したいと思います。
横スクロールのリストの箇所を見てください。
<div class="side-scroll-list-wrapper">
<ul class="side-scroll-list">
<li class="side-scroll-item">Card</li>
<li class="side-scroll-item">Card</li>
<li class="side-scroll-item">Card</li>
<li class="side-scroll-item">Card</li>
<li class="side-scroll-item">Card</li>
<li class="side-scroll-item">Card</li>
</ul>
</div>
.side-scroll-list-wrapper {
position: relative;
width: 100%;
height: 700px;
}
.side-scroll-list {
position: absolute;
display: flex;
}
今回横スクロール部分のカードレイアウトは、横並びのリストを使っています。ポイントは、リストを覆うラッパーを作り、リストそのものはposition: absolute;にしているところです。
横幅をはみ出すような長いリストをラッパーを基準に左右に動かすイメージです。
また、リストそのものは、position: absolute;とすると高さがカウントされなくなるので、heightやpaddingを使ってラッパーに高さを入れておきましょう。
JavaScript
いよいよ本題です。コードを見てもらうとわかると思いますが、とってもシンプルな記述で済んでいます。GSAPってすごい。
const listWrapperEl = document.querySelector('.side-scroll-list-wrapper');
const listEl = document.querySelector('.side-scroll-list');
gsap.to(listEl, {
x: () => -(listEl.clientWidth - listWrapperEl.clientWidth),
ease: 'none',
scrollTrigger: {
trigger: '.side-scroll',
start: 'top top',
end: () => `+=${listEl.clientWidth - listWrapperEl.clientWidth}`,
scrub: true,
pin: true,
anticipatePin: 1,
invalidateOnRefresh: true,
},
});
上から順に見ていきましょう。
const listWrapperEl = document.querySelector('.side-scroll-list-wrapper');
const listEl = document.querySelector('.side-scroll-list');
gsap.to(listEl, {
...
});
まずはアニメーションさせる横スクロールのリストを取得し、変数に入れます。そしてgsap.to()の一つ目の引数に指定すると、その要素を動かすことができます。
今回はquerySelectorで要素を取得していますが、クラス名の文字列を直接入れて使うこともできます。
gsap.to('.side-scroll-list', {
...
});
また、GSAPには様々なメソッドが用意されていて、今回使用するgsap.to()は指定した状態へ変化するといった意味合いになります。つまり、以下のようにするとtextというクラス名を持った要素をx方向に100px動かすことができます。
gsap.to('.text', {
x: 100,
});
これがGSAPの基本的な構文です。
ただし今回のデモは少し特殊なので、指定方法が少し面倒になります。今回やりたいことは、「横並びのリストを最後まで移動させること」なので、リストの長さ分だけ移動させるため、xの値はリスト要素からとってくる必要があります。
さらに、横移動し終わったリストの最後尾がラッパーの右端に合うようにしたいので、xに指定する値は「リストの横幅 – リストラッパーの横幅」という計算になります。
さらにさらに、画面のリサイズに対応するため、xの指定は数値や文字列ではなく関数で指定する必要があります。
関数で指定することで、リサイズによって要素の大きさが変わっても自動でアニメーションを計算し直してくれます。すごい。(後述するinvalidateOnRefreshオプションと合わせて使うみたいです)
説明が複雑になってしまいましたが、以下のようになります。
gsap.to(listEl, {
x: () => -(listEl.clientWidth - listWrapperEl.clientWidth),
...
});
続いて、今回の肝であるScrollTriggerの部分です。
scrollTrigger: {
trigger: '.side-scroll',
start: 'top top',
end: () => `+=${listEl.clientWidth - listWrapperEl.clientWidth}`,
scrub: true,
pin: true,
anticipatePin: 1,
invalidateOnRefresh: true,
},
ScrollTriggerを使う場合、オブジェクトをさらにネストする形でScrollTrigger用のパラメータを設定します。
ちなみに、冒頭で紹介したマーカー機能は、ここにさらに
markers: true
と追記すると使用できます。
trigger
triggerでは、その名の通りアニメーション発火の引き金となる要素を指定します。今回のアニメーションでは横スクロールするセクションを指すクラス‘.side-scroll’を指定します。
そうすると、このアニメーションはside-scrollセクションが画面内に入ってきたときに実行される、という意味になります。
start
startには、triggerに指定した要素が具体的に画面のどの部分に入ったらアニメーションを発火するのか、を指定できます。
今回のデモでは‘top top’となっているので分かりづらいですが、以下のように考えます。
start: '①triggerの要素のどの部分? ②画面のどの部分?',
つまり、今回はtriggerであるside-scrollセクションの一番上が、画面の一番上に来たらという意味になりますね。
また、top以外にもbottomやcenterといった位置指定ができる他、例えば「画面の上から30%くらいの位置に来たら」としたい場合は②の値を30%とすることで実装できます。便利ですね。
end
startがアニメーションの開始タイミングを指定するのに対し、endは終了タイミングを指定します。
startで指定したようなtopなどを使って画面内での位置を元に制御することもできますが、今回はリストの横移動の分だけアニメーションさせたいので、xで指定した値と同じ式を入れています。また、この部分も画面リサイズによって変化すす可能性があるので、関数で書く必要があります。
scrub
scrubをtrueにすることで、アニメーションはスクロールに同期します。今回はスクロールに追従するアニメーションなのでtrueにしますが、例えば「ここまでスクロールしたら画像をふわっと表示する」といったようなアニメーションの場合は、scrubはいらなくなります。
pin
こいつが今回のポイントであるpin機能で、要素をピン留めしたかのように画面に固定します。つまり、今回の横スクロールデモは、pinによって画面内にセクションを固定することで、その間はスクロールしても下には行かず、代わりにとリストが横へ動くため横スクロールしているように見える、というわけですね。
anticipatePin
pin機能を使うとき、スクロールが速すぎると画面の固定が遅れてページがガタつくことがありますが、これを指定しておくことで固定のタイミングがより早く検知されてガタつきを防ぐ効果があるそうです。
invalidateOnRefresh
最後の難しそうな英語のこいつですが、これはリサイズに関係する重要なオプションです。先ほどの解説で、値の指定を関するにすることでリサイズ時に再計算されると書きましたが、このオプションをtrueにしていないと上手くリサイズされません。
ただ正直なところ、公式のドキュメントを読んでもなぜこれが必要になってくるのか理解できなかったので、詳しい解説は割愛させていただきます…。
デモの使いどころと注意点
今回実装した横スクロール風アニメーションは、ボリュームのあるカードレイアウトをコンパクトに収めたいときなどに使えます。
通常そのようなケースではカルーセルスライダーと呼ばれるデザインがされることがほとんどですが、このデモのポイントは通常の上下スクロール動作のみですべてのカードを見せることができる点です。ドラッグやスワイプ、ボタンクリックなどでスライダーを左右に送らなくてもいいのがいいところですね。
しかし、使いすぎには注意です。すべてのカードを見せることができるということは、今回のようにセクションの途中に入れて使う場合、ユーザーはすべてのカードを見るまで下に行くことができないということになります。
コンパクトにできるからといって詰め込みすぎると、見る人をイライラさせてしまうかもしれないので気をつけましょう。
おわりに
GSAPってすごいなあと思っていただけたのではないでしょうか。実は僕もこの記事を書くタイミングで初めて使ってみたのですが、すでにGSAPなしでは生きていけません(笑)。
あまり王道なデモではありませんでしたが、既にGSAPを知っている方にも、こんなこともできるんだという新たな発見になれば嬉しいです。
最後まで読んでいただきありがとうございます! また次の記事でお会いしましょう。
LIGはWebサイト制作を支援しています。ご興味のある方は事業ぺージをぜひご覧ください。