3Dカルーセルは、複数の画像やカードを円環状に並べて立体的に回転させるUIです。結論から言うと、CSSのperspective・rotateY()・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をそのまま動かしたものです。「◀︎」「▶︎」ボタンで回転し、中央のアイテムが手前に大きく強調されます。
See the Pen 3Dカルーセル by masakazuimai (@masakazuimai) on CodePen.
3Dカルーセルの作り方(コピペ可能な実装コード)
実装は次の3ステップで完結します。いずれも外部ライブラリは不要です。
- HTML:アイテムと操作ボタンの器を用意する
- CSS:
perspectiveとtransform-styleで3D空間を作る - 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つです。親の.carouselにperspectiveを付けて視点(遠近感)を作り、各.carousel-itemにtransform-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()で等間隔に並べます。中央のアイテムだけopacityとscaleを上げて強調し、ボタンクリックで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合成を使う:
transformにwill-changeを指定し、初回のカクつきを抑える。rotateY()/translateZ()はそもそもGPU合成されます - 背面をコストダウン:非中央の
opacityを下げる(実装済み)。枚数が多い場合は遠い背面にvisibility: hiddenも検討 - transitionを絞る:アニメさせるのは
transformとopacityだけで十分。z-indexのアニメーションは効果が薄く外せる - レスポンシブ:
perspectiveとdepthをビューポート幅に応じて縮小。clamp()を使うとメディアクエリなしで可変にできる
/* GPU合成を前倒しして初回のカクつきを抑える */
.carousel-item {
will-change: transform;
}
/* 画面幅に応じて奥行きを可変にする */
.carousel {
perspective: clamp(300px, 50vw, 600px);
}よくある失敗とつまずき
3D変形が思うように効かないときは、たいてい次のどれかが原因です。
| 症状 | 原因 | 対処 |
|---|---|---|
| 奥行きが出ず平面のまま | 親にperspectiveがない | .carouselにperspectiveを指定する |
| アイテムが縦に積まる | position: absoluteの付け忘れ | 各.carousel-itemをabsoluteにする |
| 重なり順が崩れる | z-indexを更新していない | JSで中央のアイテムを最大にする |
| アイテムが枕からはみ出る | translateZが大きすぎる | depthとコンテナ高さ・overflow:hiddenのバランスを取る |
次のステップ:応用と検証
基本が動いたら、次の拡張に進むと実用レベルになります。
- 自動再生:
setIntervalでupdateCarousel("next")を定期実行し、hoverで一時停止する - スワイプ操作:pointerイベントでドラッグ量を拾い、回転角に反映させる
- 画像の動的差し替え:Picsumの代わりにJSON配列や
data-*属性からアイテムを生成する - アクセシビリティ:ボタンに
aria-labelを付け、キーボード操作(←→)にも対応する
枚数・奥行き・速度などのパターンを手早く試したい場合は、下のジェネレーターが便利です。こちらは本記事の「ライブラリなし」実装とは異なり、アニメーションライブラリGSAPを使って6タイプの3Dカルーセルを生成します。より滑らかな動きを手軽に得たいときは、設定を変えながらコピペ用コードを書き出せます。
まとめ
CSSのperspective・rotateY()・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を適用して描画負荷を軽減します。アニメさせるプロパティをtransformとopacityに絞るのも効果的です。
Q. 3Dカルーセルをレスポンシブ対応するには?
perspectiveとtranslateZの値をビューポート幅に応じて調整します。メディアクエリまたはclamp()を使い、モバイルではperspectiveを小さく、depthを短くすると画面サイズに適した奥行きを保てます。
Q. アイテム数を増やすとどうなりますか?
1枚あたりの角度は360 / totalItemsで自動計算されるため、HTMLにcarousel-itemを追加するだけで並びます。ただし枚数が多いと描画負荷が上がるため、背面の描画削減を併せて検討してください。
