3DカルーセルをCSSとJavaScriptで実装|動きのあるモダンなUI


3Dカルーセルは、複数の画像やカードを円環状に並べて立体的に回転させるUIです。結論から言うと、CSSのperspectiverotateY()translateZ()と数十行のJavaScriptだけで、ライブラリなしに実装できます。この記事では、実際に動くデモのコードをそのまま掲載しながら、作り方・カスタマイズ・パフォーマンス最適化までを解説します。

3Dカルーセルとは、複数の要素を円環状に配置し、CSSの3D変形(rotateY()translateZ())で奥行きを付けて回転させるコンポーネントです。親要素にperspectiveを指定して視点を作り、子要素をY軸回転で等間隔に並べるのが基本原理です。


3Dカルーセルとは?

3Dカルーセルは、要素を回転する円状に配置し、動的な動きと3Dエフェクトを実現するUIデザインです。平面的なスライダーよりも視線を集めやすく、次のような場面で活用されます。

  • 商品ギャラリー(複数商品を順に見せる)
  • イメージスライダー(作品・実績の紹介)
  • サービス紹介ページのヒーロー演出

立体的に見える仕組みはシンプルです。親要素にperspectiveで視点(カメラの位置)を作り、各アイテムをrotateY()で少しずつ違う角度に向けてから、translateZ()で視点側へ押し出します。こうすると全アイテムが一本の円柱の側面に貼り付いたように並び、回転角を少しずつ変えるだけでカルーセルが回って見えます。実装で使うCSSプロパティは、次の3つを押さえれば十分です。

プロパティ指定する場所役割
perspective親(.carousel視点までの距離。小さいほど遠近が強く立体的になる
transform-style: preserve-3d各アイテム子要素を平面に潰さず3D空間に保つ
transform: rotateY() translateZ()各アイテム(JSで付与)Y軸回転で向きを決め、奥行き方向に押し出して円環に配置する

実装デモ(動作サンプル)

下のデモは、この記事で解説するHTML・CSS・JavaScriptをそのまま動かしたものです。「◀︎」「▶︎」ボタンで回転し、中央のアイテムが手前に大きく強調されます。

差分チェックツールで効率UPお手本コードと自分のコードを比較して、違いを一目で確認できます。練習前にブックマークしておくと便利です。
Diff Checkerを開く →

See the Pen 3Dカルーセル by masakazuimai (@masakazuimai) on CodePen.


3Dカルーセルの作り方(コピペ可能な実装コード)

実装は次の3ステップで完結します。いずれも外部ライブラリは不要です。

  1. HTML:アイテムと操作ボタンの器を用意する
  2. CSSperspectivetransform-styleで3D空間を作る
  3. JavaScript:各アイテムをY軸回転で等間隔に配置し、ボタンで回転させる

1. HTML:マークアップ

回転させたい枚数だけcarousel-itemを並べます(ここでは7枚)。枚数はJavaScript側が自動で数えるため、HTMLを増減するだけで調整できます。

<div class="carousel-container">
  <div class="carousel">
    <div class="carousel-item"></div>
    <div class="carousel-item"></div>
    <div class="carousel-item"></div>
    <div class="carousel-item"></div>
    <div class="carousel-item"></div>
    <div class="carousel-item"></div>
    <div class="carousel-item"></div>
  </div>
</div>

<div class="button-container">
  <button class="prev">◀︎</button>
  <button class="next">▶︎</button>
</div>

2. CSS:3D空間とスタイル

ポイントは2つです。親の.carouselperspectiveを付けて視点(遠近感)を作り、各.carousel-itemtransform-style: preserve-3dを付けて子要素を3D空間に置きます。

/* 中央寄せ用(デモ用の最小スタイル) */
body {
  margin: 0;
  min-height: 100vh;
  display: flex;
  justify-content: center;
  align-items: center;
  background: #f4f4f4;
}

/* カルーセルの外枠 */
.carousel-container {
  position: relative;
  width: 100%;
  max-width: 1400px;
  height: 300px;
  margin: 0 auto;
  overflow: hidden;
}

/* 3D空間を定義する本体 */
.carousel {
  position: relative;
  width: 100%;
  height: 100%;
  display: flex;
  justify-content: center;
  align-items: center;
  perspective: 400px; /* 奥行きの強さ */
}

/* 円環に並ぶ各アイテム */
.carousel-item {
  position: absolute;
  width: 225px;
  height: 127px; /* 16:9 */
  background-size: cover;
  background-position: center;
  border-radius: 10px;
  transform-style: preserve-3d;
  opacity: 0.3;
  transition: transform 0.5s, opacity 0.5s;
}

/* 前へ/次へボタン */
.button-container {
  position: absolute;
  top: 85%;
  left: 50%;
  transform: translateX(-50%);
  display: flex;
  gap: 10px;
}

button {
  color: #0078d7;
  border: none;
  padding: 10px 20px;
  cursor: pointer;
  font-size: 16px;
}

/* レスポンシブ対応 */
@media (max-width: 768px) {
  .carousel-item { width: 168.75px; height: 95px; }
  .carousel-container { height: 250px; }
}

@media (max-width: 480px) {
  .carousel-item { width: 135px; height: 76px; }
  .carousel-container { height: 200px; }
}

3. JavaScript:回転の制御

アイテム数から1枚あたりの角度(360 / totalItems)を求め、rotateY()で等間隔に並べます。中央のアイテムだけopacityscaleを上げて強調し、ボタンクリックでcurrentRotationを加減して回転させます。

document.addEventListener("DOMContentLoaded", () => {
  const items = document.querySelectorAll(".carousel-item");
  const totalItems = items.length;
  let currentIndex = 0;    // 中央に表示するアイテムの番号
  let currentRotation = 0; // 現在の回転角度

  // 各アイテムにサンプル画像を設定(Picsum)
  items.forEach((item, index) => {
    const width = 200;
    const height = Math.round(width * 9 / 16); // 16:9
    item.style.backgroundImage = `url('https://picsum.photos/${width}/${height}?random=${index + 1}')`;
  });

  const updateCarousel = (direction) => {
    const angle = 360 / totalItems; // 1アイテムあたりの角度
    const depth = 400;              // translateZ(奥行き=円の半径)

    if (direction === "next") {
      currentIndex = (currentIndex + 1) % totalItems;
      currentRotation -= angle;
    } else if (direction === "prev") {
      currentIndex = (currentIndex - 1 + totalItems) % totalItems;
      currentRotation += angle;
    }

    items.forEach((item, index) => {
      const rotateAngle = angle * index + currentRotation;
      if (index === currentIndex) {
        item.style.opacity = "1";
        item.style.transform = `rotateY(${rotateAngle}deg) translateZ(${depth}px) scale(1.1)`;
        item.style.zIndex = "10";
      } else {
        item.style.opacity = "0.3";
        item.style.transform = `rotateY(${rotateAngle}deg) translateZ(${depth}px) scale(1)`;
        item.style.zIndex = "1";
      }
    });
  };

  document.querySelector(".next").addEventListener("click", () => updateCarousel("next"));
  document.querySelector(".prev").addEventListener("click", () => updateCarousel("prev"));

  updateCarousel(); // 初期表示
});

カスタマイズポイント

主要なパラメータを調整するだけで、見た目と動きを大きく変えられます。

パラメータ役割調整のヒント
アイテム数(.carousel-item円環に並ぶ枚数増やすほど角度(360/枚数)が狭まり密に。HTMLを増減するだけ
perspective奥行きの強さ小さいほど遠近が強調され立体的。大きいほど平面的
translateZ の depth円の半径大きいほどアイテムが手前に大きく出る。コンテナ高さと調整
opacity(非中央)中央の強調度下げるほど中央のアイテムが際立つ
transition回転の滑らかさ0.5s前後。短いほどキビキビ、長いほどゆったり

パフォーマンス最適化とレスポンシブ対応

アイテム数が多いと回転時にカクつくことがあります。以下のポイントを押さえると、動きを滞らせず、どの画面サイズでも見やすく保てます。

  • GPU合成を使うtransformwill-changeを指定し、初回のカクつきを抑える。rotateY()/translateZ()はそもそもGPU合成されます
  • 背面をコストダウン:非中央のopacityを下げる(実装済み)。枚数が多い場合は遠い背面にvisibility: hiddenも検討
  • transitionを絞る:アニメさせるのはtransformopacityだけで十分。z-indexのアニメーションは効果が薄く外せる
  • レスポンシブperspectiveとdepthをビューポート幅に応じて縮小。clamp()を使うとメディアクエリなしで可変にできる
/* GPU合成を前倒しして初回のカクつきを抑える */
.carousel-item {
  will-change: transform;
}

/* 画面幅に応じて奥行きを可変にする */
.carousel {
  perspective: clamp(300px, 50vw, 600px);
}

よくある失敗とつまずき

3D変形が思うように効かないときは、たいてい次のどれかが原因です。

症状原因対処
奥行きが出ず平面のまま親にperspectiveがない.carouselperspectiveを指定する
アイテムが縦に積まるposition: absoluteの付け忘れ.carousel-itemabsoluteにする
重なり順が崩れるz-indexを更新していないJSで中央のアイテムを最大にする
アイテムが枕からはみ出るtranslateZが大きすぎるdepthとコンテナ高さ・overflow:hiddenのバランスを取る

次のステップ:応用と検証

基本が動いたら、次の拡張に進むと実用レベルになります。

  • 自動再生setIntervalupdateCarousel("next")を定期実行し、hoverで一時停止する
  • スワイプ操作:pointerイベントでドラッグ量を拾い、回転角に反映させる
  • 画像の動的差し替え:Picsumの代わりにJSON配列やdata-*属性からアイテムを生成する
  • アクセシビリティ:ボタンにaria-labelを付け、キーボード操作(←→)にも対応する

枚数・奥行き・速度などのパターンを手早く試したい場合は、下のジェネレーターが便利です。こちらは本記事の「ライブラリなし」実装とは異なり、アニメーションライブラリGSAPを使って6タイプの3Dカルーセルを生成します。より滑らかな動きを手軽に得たいときは、設定を変えながらコピペ用コードを書き出せます。


まとめ

CSSのperspectiverotateY()translateZ()と、アイテムを等間隔に並べる数十行のJavaScriptだけで、ライブラリなしの3Dカルーセルが作れます。

まずは本記事のコードをそのままコピーして動かし、perspectiveやdepthを変えて見た目の変化を試してみてください。その上で自動再生やスワイプを加えれば、モダンなUIに仕上がります。


よくある質問(FAQ)

Q. 3DカルーセルをCSSとJavaScriptだけで実装できますか?

はい。CSSのrotateY()translateZ()を組み合わせ、親要素にperspectiveを指定すれば、ライブラリなしで立体的な回転エフェクトが作れます。JavaScriptはボタンやスワイプでの回転制御に使います。

Q. Swiperなどのライブラリとどちらがよいですか?

軽量にしたい、デザインを自由に作りこみたい、仕組みを学びたい場合は本記事のような自作が向いています。一方で、無限ループ・ページネーション・タッチ慣性などを短期間で網羅したい場合は、Swiper等のライブラリが効率的です。

Q. 3Dカルーセルのパフォーマンスを改善するには?

will-change: transformを指定してGPU合成を前倒しします。また、表示範囲外のアイテムにvisibility: hiddenや低いopacityを適用して描画負荷を軽減します。アニメさせるプロパティをtransformopacityに絞るのも効果的です。

Q. 3Dカルーセルをレスポンシブ対応するには?

perspectivetranslateZの値をビューポート幅に応じて調整します。メディアクエリまたはclamp()を使い、モバイルではperspectiveを小さく、depthを短くすると画面サイズに適した奥行きを保てます。

Q. アイテム数を増やすとどうなりますか?

1枚あたりの角度は360 / totalItemsで自動計算されるため、HTMLにcarousel-itemを追加するだけで並びます。ただし枚数が多いと描画負荷が上がるため、背面の描画削減を併せて検討してください。