流れ続けるカルーセルの実装、Splide・GSAP・CSSをどう使い分けるか
下記のような流れ続けるカルーセル(スライダー)を実装しようとしたとき、カルーセルのライブラリを使うべきか、GSAPにすべきか、CSSアニメーションだけで済ませるか迷った経験はないでしょうか。私は結構な頻度で迷います。
過去にはこのような経験もありました。
- GSAPで実装したが、後からスワイプ操作が必要になり、カルーセルライブラリに乗り換えた
- CSSアニメーションで実装したが、カクつきが発生し修正に時間がかかった
そこで、自分なりの指針を整理してみることにしました。この記事では自動で流れ続けるカルーセルの個人的な実装方針について書き留めています。
結論
私は実務では以下のように使い分けることが多いです。
- ユーザーのアクションが入る場合:カルーセルライブラリ(Splide)
- スクロールに連動して動かす場合:アニメーションライブラリ(GSAP)
- 上記以外:CSSアニメーション
表にまとめると次のようになります。
| Splide | GSAP | CSSアニメーション | |
|---|---|---|---|
| 適している例 | スワイプ・ページ送りなどのユーザー操作がある | スクロールに連動して速度・方向等が変わる | シンプルな自動スクロール、斜め方向など |
| 導入コスト | 本体+拡張(約30KB) | 本体+ScrollTrigger(約30KB) | ライブラリ不要 |
| アニメーションの自由度 | ライブラリの仕様に依存 | 高い | 中程度(複雑な制御は困難) |
| アクセシビリティ | ライブラリが一定程度カバー | 自前で対応が必要 | 自前で対応が必要 |
それぞれの実装パターンについて個別に見ていきます。
カルーセルライブラリを使用するケース
今回はSplideを使用しました。
自動スクロールを導入する場合にはAuto Scrollのエクステンションが必要です。下記が実装例です。
カルーセルライブラリで導入したスライダーの特徴としては、ユーザー操作の実装をスムーズに行える点です。上記の例では左右にスワイプができ、「前へ」「次へ」のページ送りのボタンが機能するようにオプションを設定しています。
const options = { type: "loop", autoWidth: true, gap: "10px", pagination: false, arrows: true, // 前へ・次へのページ送りを表示するかどうか(デフォルトでture) drag: true, // ドラッグによりスライダーを動かせるかどうか(デフォルトでture) autoScroll: { speed: 0.7, pauseOnFocus: false, pauseOnHover: false, },};Splideではアクセシビリティについても一定程度担保してくれるのが良い点です。
デメリットとしては、ライブラリの仕様に縛られるため、複雑なカスタマイズを行おうとするとコストがかかります。またカスタマイズによっては実装困難な場合があります。
例えばSplideでは斜め方向に自動スクロールさせようとtransform: rotate(45deg);を記述すると、表示が崩れます。これはSplideが内部でtransformを使ってスライドの位置を制御しているため、外側からrotateを重ねると競合してしまうためです(Swiperでは問題なさそうでした)。
メリット・デメリットをまとめると次のようになります。
| メリット | デメリット |
|---|---|
|
|
実際の使用例としては、採用ページによくある「数字で見るXXX」をカード形式のスライドとして見せる場合に、カルーセルライブラリを使用するようにしています。
アニメーションライブラリを使用するケース
次にアニメーションライブラリを使用するケースです。今回はGSAPを使用しました。
まずはシンプルな流れ続けるカルーセルを実装した例です。
GSAPの特徴としてはとても少ない量のコードで実装できる点があります。上記の例ではわずか7行のJavaScriptのコードで動いています。
const lists = gsap.utils.toArray('[data-infinite-carousel="list"]');
const loop = gsap.to(lists, { xPercent: -100, ease: "none", duration: 30, repeat: -1,});そしてGSAPが得意なスクロール連動の動きも実装することができます。
次の例ではスクロールの速度(強度)に応じて、スライダーの動く速度も早くなるようにしています。スクロールに連動した動きを実装するため、ScrollTriggerを読み込んでいます。
近年のWebサイトでよく見かける動きですよね。他にスクロールの方向に応じてスライドが移動する向きを反転させる動きを実装することも可能です。
GSAPでカルーセルを実装するデメリットとしては、ドラッグ操作やページ送りなどのユーザー操作には不向きな点です。GSAPはあくまでもアニメーションに関する動きを制御するだけなので、ユーザー操作を実現するには全て自前で実装する必要があります。
メリット・デメリットをまとめると次のようになります。
| メリット | デメリット |
|---|---|
|
|
スクロール連動やリッチな動きが求められる場合は、GSAPで実装するのが最も適しています。
CSSアニメーションを使用するケース
最後にCSSアニメーションを使用するケースです。HTMLとCSSのみで完結するため、ライブラリを読み込む必要はありません。下記が実装例です。
最大のメリットはコード量が少なくシンプルなことでしょう。画像(スライド)のリストを横並びにし、translateが変化する@keyframesを設定するのみで機能します。
.c-infinite-carousel { display: flex; overflow: hidden;}
.c-infinite-carousel__list { display: flex; animation: infinity-scroll-from-left 30s infinite linear;}
@keyframes infinity-scroll-from-left { from { transform: translateX(0); } to { transform: translateX(-100%); }}画像(スライド)のリストをtranslateで移動させているだけなので、次のような斜め方向への無限スクロールも簡単に実装できます。以下はtransform: rotate(-20deg);を設定した例です。
デメリットとしてはリストの境目で、カクつきが発生しやすいことです。特に要素間の余白をcolumn-gapで設定しているとカクつきが発生しやすい印象です。
column-gapでカクつきが発生しやすい理由は、translateX(-100%)の基準となる幅の計算にあります。translateX(-100%)はその要素自身の幅を基準に移動量を計算しますが、column-gapで設定した余白はその幅に含まれません。そのため、アニメーションがループして最初の位置に戻る際に、余白分だけ位置がずれてカクつきが発生します。
一方でmarginで余白を設定した場合は、余白を含めた位置の計算がtranslateX(-100%)と一致しやすくなるため、ループ時のずれが発生しにくくなります。
/*カクつきが発生しやすいコード例*/.c-infinite-carousel { display: flex; overflow: hidden; /* column-gapでリスト間の余白を設定 */ column-gap: 10px;}
.c-infinite-carousel__list { display: flex; /* column-gapでスライド間の余白を設定 */ column-gap: 10px; animation: infinity-scroll-from-left 30s infinite linear;}
.c-infinite-carousel__item { width: 200px; aspect-ratio: 1;}
/*カクつきを抑えたコード例*/.c-infinite-carousel { display: flex; overflow: hidden;}
.c-infinite-carousel__list { display: flex; animation: infinity-scroll-from-left 30s infinite linear;}
.c-infinite-carousel__item { /* margin-leftで余白を設定 */ margin-left: 10px; width: 200px; aspect-ratio: 1;}メリット・デメリットをまとめると次のようになります。
| メリット | デメリット |
|---|---|
|
|
近年登場したCSS カルーセル機能を使用すれば、一般的なユーザー操作についてはCSSのみで実装することも可能なようです。ただし未実装のブラウザもあるため、実務で使用するにはまだ先になりそうです。
prefers-reduced-motionへの対応
流れ続けるカルーセルは自動で動き続けるため、OSで「視差効果を減らす」が有効な環境への配慮が必要です。どの手法を選んでも、アニメーションを停止する対応を入れておくと安心です。
Splideの場合はreducedMotionオプションが用意されており、autoScroll: falseを設定することで自動スクロールを停止できます。
const options = { // ...省略 // (prefers-reduced-motion: reduce)を検出した際に使用されるオプション reducedMotion: { autoScroll: false, },};GSAPの場合はgsap.matchMedia()を使用して分岐できます。
const mm = gsap.matchMedia();
// 視差効果を減らす設定がされていない環境でのみ実行mm.add("(prefers-reduced-motion: no-preference)", () => { gsap.to(lists, { xPercent: -100, ease: "none", duration: 30, repeat: -1, });});CSSアニメーションの場合は、先述のコード例に以下のメディアクエリを追加することで対応できます。
.c-infinite-carousel__list { display: flex;
/* 視差効果を減らす設定がされていない環境でのみ実行 */ @media (prefers-reduced-motion: no-preference) { animation: infinity-scroll-from-left 30s infinite linear; }}停止ではなくスクロール速度を落とすだけにする考え方もありますが、中途半端な動きがかえって違和感を生むケースがあるため、停止で統一しています。
まとめ
流れ続けるカルーセルの実装方法を3パターン見てきました。それぞれ適しているケースが異なるため、仕様が決まった段階で実装方法を選べると、無駄な手戻りを減らせると感じています。
今回は「流れ続けるカルーセル」に絞った話でしたが、カルーセル以外のUIコンポーネントについても同様に自分なりの判断基準を整理していきたいと思っています。
