12周年記念パーティ開催! 2024/5/10(金) 19:00

ライブラリなしで実装する定番UI 第9回 カルーセルUI:要件決定とアニメーション動作の作成

ECサイトやコーポレートサイトでよく見かける、カルーセルUIの実装を解説していきます。ライブラリやプラグインは数多くありますが、基本的な作り方を学ぶことでさまざまな要件に対応できるようになりましょう。

発行

著者 國仲 義則 フロントエンド・エンジニア
ライブラリなしで実装する定番UI シリーズの記事一覧

カルーセルUIについて

カルーセルUIは、ウェブサイトを巡っているとかなりの頻度で見かけるものではないでしょうか。特に、ECサイトやコーポレートサイトでは、タブUIと並んでよく使われている印象があります。そのUIの呼び方も多様ですが*、このシリーズでは「カルーセルUI」として扱います。

*注:カルーセルUIの呼び方

カルーセルUIのその他の呼び方としては、単に「スライダー」、特に画像のものについては「イメージスライダー」などがあります。アニメーションによっては「クロスフェードのギャラリー」のような呼び方をされることもあります。

ユーザビリティの観点や、その効果が低いという意見(ユーザーはカルーセルUI部分をほとんど見ていない、または使っていない、など)もありますが、ウェブサイトの受託を行っている開発者は避けては通れないUIかと思われます。

カルーセルUIは「1つのエリア内でパネルが次々と切り替わる」というシンプルな動作ゆえに非常に多くのパターンが存在します。

  • 自動で勝手に切り替わるのか
  • 1つ前のパネル、1つ次のパネルへ移動するボタンはあるのか
  • 任意のパネルへ移動するボタンはあるのか
  • 最後のパネルから最初のパネルに戻るときは次のパネルとして扱うのか、一気に最初に戻るのか(逆も同様)
  • 順番をいくつかスキップして移動するとき、間のパネルも表示するのか
  • パネル単位ではなく、複数のアイテムのいくつかを表示し、少しずつ移動するのか
  • スワイプ動作にパネルが追従するのか、それともフリック動作で移動するのか
  • アクティブではないパネルは表示するのか
  • などなど……

少し考えただけでもこれくらいは出てきます。アニメーションのパターンも含め、実際の要件はもっと細かく決められることでしょう。

そういった特性を持つものですから、単独動作のライブラリや、jQueryなどの大きなライブラリのプラグインが昔から数多く存在します。実案件では「このライブラリを使ってください」という指定もあるかもしれません。しかし、そうではない場合は要件に合ったライブラリを探す必要がでてきます。それがうまくマッチしない場合はカスタムするか、自作するしかありません。

このシリーズでは、カルーセルUIの基本的な作り方を学びます。基本的なものが作れるようになれば、あとはさまざまな要件に対応していくことで作れるパターンも増えていきます。

このシリーズで作るカルーセルUIの要件

基本的なカルーセルUIの動作といってもいくつかパターンがありますが、ここでは次のような要件を必須条件として作成していきます。

  1. 複数のバナー画像をまとめるカルーセルUIのように、パネルが1枚ずつ切り替わる
  2. 自動切り替わり機能あり
  3. 自動で最後のパネルに到達したら、次は最初のパネルに一気に戻る(最初のパネルを最後のパネルの次という扱いはしない)
  4. 1つ前のパネル、1つ次のパネルへ移動するボタンあり
  5. 最初のパネルがアクティブの場合、1つ前へ移動するボタンは非アクティブにする
  6. 最後のパネルがアクティブの場合、1つ次へ移動するボタンは非アクティブにする
  7. 任意のパネルへ移動するボタンあり(インジケータなどと呼ばれる機能)
  8. 順番をスキップする場合、移動中に間のパネルも表示される
  9. スワイプ動作にパネルが追従する
  10. 可変幅である

完成時の大雑把な見た目は、次のようになります。

また、対象とするブラウザは以下とします(バージョン指定がないものは最新バージョン)。

  • Google Chrome
  • Mozilla Firefox
  • Internet Explorer 11 (Windows 10)
  • Microsoft Edge (EdgeHTML)
  • Safari
  • iOS Safari
  • Android Chrome

アニメーションの対象となる要素を考える

カルーセルUIには切り替わり時のアニメーションがつきものです。まずはどの要素をアニメーションの対象とすれば楽に作れるのか考えます。

今回の要件では、

  • 自動で最後のパネルに到達したら、次は最初のパネルに一気に戻る(最初のパネルを最後のパネルの次という扱いはしない)
  • 順番をスキップする場合、移動中に間のパネルも表示される

という条件があるため、パネルは常に一定の配置のままで問題ないことがわかります。パネルは同じ配置にしておき、その親を子のパネルが表示できる位置まで移動させればアニメーション対象が1つで済みます*。

*注:パネル自体をアニメーションさせたほうがよい場合

パネル自体をアニメーションの対象としたほうがよい場合というのは、たとえば、フェードイン・フェードアウトで切り替わる場合でしょう。この場合は親を移動させる必要がないので、パネルをすべて重ねて配置しておき、フェードイン・フェードアウトの2種類を2つのパネルに対して行います。

文字だけで説明してもわかりづらいと思いますので、実際に動きのサンプルを作ってみましょう。

<div class="Carousel">
  <div class="Carousel-content">
    <div class="Carousel-item">A</div>
    <div class="Carousel-item">B</div>
    <div class="Carousel-item">C</div>
    <div class="Carousel-item">D</div>
    <div class="Carousel-item">E</div>
    <div class="Carousel-item">F</div>
  </div>
</div>

この場合、.Carouselがパネルの表示部分になります。そして、アニメーションの対象とするのはパネル.Carousel-itemの親である.Carousel-contentです。

.Carousel {
  margin: 10px;
  width: 100px;
  /* 範囲表示用 */
  outline: 3px solid black;
  outline-offset: 3px;
}

.Carousel-content {
  display: flex;
  flex-wrap: nowrap;
  width: 100%;
  /* 範囲表示用 */
  outline: 3px solid yellow;
}

.Carousel-item {
  flex-grow: 0;
  flex-shrink: 0;
  flex-basis: 100%;
  height: 100px;
  /* 以下内部テキストの表示用 */
}

/* 以下アイテムの色指定 */

これらの結果が次のデモです。

今回の要件は可変幅ですが、ここでは動作確認のために固定幅を指定してあります。

CSS Flexible Boxを利用して、パネルを折り返しなしの横並びに配置しています。ここでのポイントは、Flexコンテナとなる.Carousel-contentの幅を親の100%にし、パネルもそれに合わせていることです。パネルの2枚目以降が.Carousel-contentからはみ出てしまいますが、.Carousel-contentoverflowプロパティ値がvisibleであれば問題ありません。

要素の移動はCSS Transformsを使いますので、こうすることでパネルn個分を移動幅を出すとき100% * nで計算できるようになり、100% / パネル数 * nのように端数が出る可能性をなくせます。

これがどういうことか、図で解説しましょう。次の2つの画像は、緑の枠線部分が.Carouselの範囲、黒の枠線部分が.Carousel-contentとなっています。

.Carouselに対して、.Carousel-content.Carousel-itemの幅が100%の状態

この状態ですと、.Carousel-contentを左に100%移動すればBのパネルが緑の枠内に合わせられます。これによって、移動距離は常に100% * nを保てます。

.Carouselに対して、.Carousel-contentの幅が100%、.Carousel-itemの幅は6分の1の状態

対して、.Carousel-contentを内包コンテンツ幅、つまり、.Carouselの幅にパネルの数をかけた幅にした場合、Bのパネルを緑の枠内に合わせるには.Carousel-contentを左に16.6666...%移動する必要があります。100% / パネル数 * nなので、この数値はパネルの枚数によって変化します。

参考記事:CSS Flexible BoxとCSS Transforms

CSS Flexible BoxとCSS Transformsについては、CodeGridの次のシリーズを参考にしてください。

それでは、この指定方法で問題ないか確かめてみます。JavaScriptを書いてもよいのですが、DevToolsなどを使っても問題ありませんので、ここではDevToolsを使います。

Chromeの場合、DevToolsで対象の要素.Carousel-contentを選択し、コンソールから操作を行います。

それでは、Cのパネルを表示エリアに移動してみます。

パネルを左に移動するには-100% * ntranslateX()の引数に指定します。注意することは、Aが表示エリアにある状態はtranslateX(0)と同等であるので、このときのnは0となることです。何枚目を表示エリアに移動するか、ではなく、何枚分を左に移動するかと考えるとわかりやすいです。もちろん、パネルのインデックスを指定すると考えてもいいです。

Cは3番目なので2枚分移動となり、その移動距離は-100% * n-200%です。よってこの値をtranslateX()の引数に指定し、transformプロパティ値とします。

$0.style.transform = "translateX(-200%)"

$0はDevTools上で選択された要素*です(ここでは.Carousel-content)。これをコンソールに入力し、Enterで決定します。

*注:DevToolsで使える機能

$0だけでなく、さまざまな機能があります。詳しくは公式リファレンスをご覧ください。

黒い枠に囲まれた中が「C」のパネルになりました。

実際にアニメーションをつける

それでは、アニメーションをつけてみましょう。

.Carousel-content {
  display: flex;
  flex-wrap: nowrap;
  width: 100%;
  /* トランジション周りの指定を追加 */
  transform: translateX(0);
  transition-property: transform;
  transition-duration: 0.2s;
  transition-timing-function: ease;
  /* 範囲表示用 */
  outline: 3px solid yellow;
}

前述のとおり、要素の移動はCSS Transformsを使いますので、transform: translateX(0)を追加しておきます。

合わせて、状態変化をアニメーションで表現するCSS Transitionsの指定も行います。これは仮の指定として、何でもいいので指定しておきます。アニメーションの細かい調整などは最後に行ったほうがよいでしょう。

アニメーションがうまく動いているのかの確認は先ほどのようにDevToolsで行っても問題ありませんが、簡単なJavaScriptを書いて確認してみます。

<div class="Carousel">
  ...
</div>

<button type="button">パネルを移動する</button>
function counter(limit) {
  let count = 0;

  return function () {
    count++;
    if (!(count < limit)) {
      count = 0;
    }
    return count;
  }
}

const target = document.querySelector(".Carousel-content");
const current = counter(target.childElementCount);
const button = document.querySelector("button");

button.addEventListener("click", function (event) {
  event.preventDefault();
  const value = current() * -100;
  target.style.transform = "translateX(" + value + "%)";
}, false);

一時的にボタンを追加し、それをクリックしたら.Carousel-contentが移動し、遷移アニメーションが行われる、という内容です。

指定通りのアニメーションが行われています。最後から最初に戻る際も一気に戻るようになっており、問題ありません。

今回のまとめと次回について

今回のまとめとしては、アニメーション動作要件を実現するためのHTMLとCSSを考え、柔軟に対応していくことです。

もしアニメーションがクロスフェードならば横並びにする必要はなく、表示エリアのHTMLはパネルの親で済ませられます。そのかわりにパネルを同じ位置に同じサイズで重ねて配置する必要が出てきます。

単に横にスライドするアニメーションならば、今回のようにパネルを横並びにして親を動かすことに手間がかからないでしょう。

このシリーズでは行いませんが、アニメーションの実現をJavaScriptのみで行っても問題ありません。requestAnimationFrameで行ってもよいですし、可能ならばWeb Animation APIを使うのもよいでしょう。

参考記事:requestAnimationFrameとWeb Animation API

CodeGridの過去記事で紹介されていますので、ぜひ参考にしてください。

また、Web Animation APIについては仕様も参考にしてください。

ここまでで、今回の要件のカルーセルUIを作成するためのレイアウトとアニメーションが完成しましたが、UIとして機能していません。次回からは、カルーセルUIとして操作可能になるように、作っていきます。